OLAP for WinForms
大規模なデータソース
クイックスタート > 大規模なデータソース

これまで説明したすべての例では、すべてのデータをメモリにロードしました。これは、簡単で便利に実行できる方法であり、多くの場合に有効です。

しかし、データが多すぎて一度にメモリにロードできない場合があります。たとえば、行数が百万以上あるテーブルを考えてみます。すべてのデータをメモリにロードできたとしても、このプロセスには長い時間がかかります。

これらのシナリオに対処するには、多くの方法があります。サーバー側でデータを集計およびキャッシュするクエリーを作成するか、特別な OLAP データプロバイダを作成します。どちらの場合も、C1Olap で使用可能なテーブルが作成されます。

よりシンプルな選択肢もあります。データベースには数千社の企業に関する情報が保持されており、ユーザーは一度に数社だけを確認したいとします。クライアント側で発生する C1Olap のフィルタ処理機能だけに依存するのではなく、作業の一部をサーバーに任せ、ユーザーが確認する企業だけをロードします。これは実現が簡単で、サーバーに特別なソフトウェアや構成は必要ありません。

たとえば、次の CachedDataTable クラス(このクラスは、C1Olap にインストールされた "SqlFilter" に使用されます)について考えます。

コード
コードのコピー
/// <summary>
/// <see cref="DataTable"/> クラスを拡張し、オンデマンドでデータを
/// ロードおよびキャッシュできるようにします。そのために、<see cref="Fill"/> メソッドは、
/// 一連のキーをパラメータとして受け取ります。
/// </summary>
class CachedDataTable : DataTable
{
  public string ConnectionString { get; set; }
  public string SqlTemplate { get; set; }
  public string WhereClauseTemplate { get; set; }
  Dictionary<object, bool> _values =
     new Dictionary<object, bool>();
 
  // constructor
  public CachedDataTable(string sqlTemplate, 
    string whereClauseTemplate, string connString)
  {
    ConnectionString = connString;
    SqlTemplate = sqlTemplate;
    WhereClauseTemplate = whereClauseTemplate;
  }
 
  // まだ必要な値があれば、テーブルに追加します
  public void Fill(IEnumerable filterValues, bool reset)
  {
    // 要求されている場合は、テーブルをリセットします
    if (reset)
    {
      _values.Clear();
      Rows.Clear();
    }
 
    // 新しい値のリストを取得します
    List<object> newValues = GetNewValues(filterValues);
    if (newValues.Count > 0)
    {
      // SQL 文とデータアダプタを取得します
      var sql = GetSqlStatement(newValues);
      using (var da = new OleDbDataAdapter(sql, ConnectionString))
      {
        // テーブルに新しい値を追加します
        int rows = da.Fill(this);
      }
    }
  }
  public void Fill(IEnumerable filterValues)
  {
    Fill(filterValues, false);
  }

このクラスは、通常の DataTable クラスを拡張し、Fill メソッドを提供します。このメソッドは、提供された値のリストに基づいて、テーブルを完全に再作成することも、レコードを追加することもできます。たとえば、最初はテーブルに(数千人の顧客のうち)2人の顧客を設定し、その後、ユーザーの要求があるときにだけ顧客を追加するという処理が考えられます。

コードは、OleDbDataAdapter を使用します。これを使用する理由は、サンプルが MDB ファイル(データソースとして)と OleDb スタイルの接続文字列を使用するからです。このクラスを SQL Server のデータソースで使用するために、OleDbDataAdapterSqlDataAdapter に置き換えます。

上記のコードには、以下に示す2つのシンプルなメソッドが実装されていませんでした。

コード
コードのコピー
// まだ現在の値のコレクションにないフィルタ値のリストを取得し、
  // それらのすべてを 
  // 現在の値のコレクションに追加します
  List<object> GetNewValues(IEnumerable filterValues)
  {
    var list = new List<object>();
    foreach (object value in filterValues)
    {
      if (!_values.ContainsKey(value))
      {
        list.Add(value);
        _values[value] = true;
      }
    }
    return list;
  }
 
  // テーブルに新しい値を追加する SQL 文を取得します
  string GetSqlStatement(List<object> newValues)
  {
    return string.Format(SqlTemplate, GetWhereClause(newValues));
  }
  string GetWhereClause(List<object> newValues)
  {
    if (newValues.Count == 0 || string.IsNullOrEmpty(WhereClauseTemplate))
    {
      return string.Empty;
    }
 
    // 値のリストを作成します
    StringBuilder sb = new StringBuilder();
    foreach (object value in newValues)
    {
      if (sb.Length > 0) sb.Append(", ");
      if (value is string)
      {
        sb.AppendFormat("'{0}'", ((string)value).Replace("'", "''"));
      }
      else
      {
        sb.Append(value);
      }
    }
 
    // Where 節を作成します
    return string.Format(WhereClauseTemplate, sb);
  }
}

GetNewValues メソッドは、ユーザーから要求済みで DataTable にはまだ存在していない値のリストを返します。これらは、追加する必要がある値です。

GetSqlStatement メソッドは、ユーザーから要求済みでまだロードされていないレコードをロードするための WHERE 節を含む、新しい SQL 文を作成します。このメソッドは、コンストラクタで呼び出し元が提供する文字列テンプレートを使用しているため、このクラスは汎用的です。

CachedDataTable の準備が整ったので、次の手順では、これを C1Olap に接続します。これによってユーザーは、データがすべてメモリにロードされているように、データを透過的に分析することができます。

これには、メインフォームを開き、C1OlapPage コントロールを追加し、次のコードをフォームに追加します。

コード
コードのコピー
public partial class Form1 : Form
{
  List<string> _customerList;
  List<string> _activeCustomerList;
  const int MAX_CUSTOMERS = 12;

これらのフィールドには、データベース内のすべての顧客の完全なリスト、現在ユーザーによって選択されている顧客のリスト、および任意の一時点で選択可能な顧客の最大数が含まれます。ユーザーが大量のデータを一度にアプリケーションにロードできないように、顧客の最大数は比較的小さな値に設定します。

ユーザーが必要な顧客を選択できるように、データベース内のすべての顧客の完全なリストを取得する必要があります。このリストは、件数は多いもののサイズは大きくありません。リストには顧客名のみが含まれ、注文や注文詳細などの関連付けられた詳細は含まれません。次に、顧客の完全なリストをロードするコードを示します。

コード
コードのコピー
public Form1()
{
  InitializeComponent();
 
  // 顧客の完全なリストを取得します
  _customerList = new List<string>();
  var sql = @"SELECT DISTINCT Customers.CompanyName" +
    "AS [Customer] FROM Customers";
  var da = new OleDbDataAdapter(sql, GetConnectionString());
  var dt = new DataTable();
  da.Fill(dt);
  foreach (DataRow dr in dt.Rows)
  {
    _customerList.Add((string)dr["Customer"]);
  }

次に、ユーザーが検索する顧客のリストが必要です。このリストは、プロパティ設定として存続させるため、セッションをまたいで維持されます。設定は、"Customers" と呼ばれ、"StringCollection" 型を持ちます。これを作成するには、ソリューションエクスプローラーでプロジェクトノードを右クリックし、[プロパティ]を選択して、前回と同じように[設定]タブを選択します。

0

次に、新しい設定から "アクティブな" 顧客リストをロードするコードを示します。

コード
コードのコピー
// アクティブな顧客リストを取得します
  _activeCustomerList = new List<string>();
  foreach (string customer in Settings.Default.Customers)
  {
    _activeCustomerList.Add(customer);
  }

これで、CachedDataTable を作成して DataSource プロパティに割り当てる準備が整いました。

コード
コードのコピー
// データを CachedDataTable に取得します
  var dtSales = new CachedDataTable(
    Resources.SqlTemplate, 
    Resources.WhereTemplate, 
    GetConnectionString());
  dtSales.Fill(_activeCustomerList);
 
  // データを C1OlapPage コントロールに割り当てます
  _c1OlapPage.DataSource = dtSales;
 
  // デフォルトビューを表示します
  var olap = _c1OlapPage.OlapEngine;
  olap.BeginUpdate();
  olap.RowFields.Add("Customer");
  olap.ColumnFields.Add("Category");
  olap.ValueFields.Add("Sales");
  olap.EndUpdate();

CachedDataTable コンストラクタは、3つのパラメータを使用します。

これでデータソースの準備ができたので、これを C1Olap に接続して以下を確認する必要があります。

  1. ユーザーが C1Olap フィルタ内のすべての顧客を表示できる(現在ロードされている顧客だけでなく)。
  2. ユーザーがフィルタを変更すると、新しいデータがロードされ、要求された新しい顧客が表示される。

1番を実現するためには、顧客の完全なリストを C1OlapField.Values プロパティに割り当てる必要があります。このプロパティには、フィルタで表示される値のリストが含まれます。デフォルトでは、C1Olap により、このリストには生データから検出された値が挿入されます。この場合、生データに含まれているのはリストの一部分だけなので、代わりに完全なバージョンを提供する必要があります。

2番を実現するためには、PropertyChanged イベントを監視する必要があります。このイベントは、ユーザーがフィルタを含む任意のフィールドプロパティを変更すると発生します。このイベントが発生したら、ユーザーによって選択された顧客のリストを取得し、そのリストをデータソースに渡します。

以下は、これを実現するコードです。

コード
コードのコピー
// カスタムフィルタ:リスト内の顧客、現在アクティブな顧客
  var field = olap.Fields["Customer"];
  var filter = field.Filter;
  filter.Values = _customerList;
  filter.ShowValues = _activeCustomerList.ToArray();
  filter.PropertyChanged += filter_PropertyChanged;

次は、フィルタが変更されたときにデータソースを更新するイベントハンドラです。

コード
コードのコピー
// 選択された顧客のリストが変更されたら、データベースを再クエリーします
void filter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
  // 親フィルタへの参照を取得します
  var filter = sender as C1.Olap.C1OlapFilter;
 
  // フィルタが受け入れた値のリストを取得します
  _activeCustomerList.Clear();
  foreach (string customer in _customerList)
  {
    if (filter.Apply(customer))
    {
      _activeCustomerList.Add(customer);
    }
  }
 
  // 値が選択されなかった場合はスキップします
  if (_activeCustomerList.Count == 0)
  {
    MessageBox.Show(
      "No customers selected, change will not be applied.",
      "No Customers");
    return;
  }
 
  // 必要に応じてリストを削除します
  if (_activeCustomerList.Count > MAX_CUSTOMERS)
  {
    MessageBox.Show(
      "Too many customers selected, list will be trimmed.",
      "Too Many Customers");
    _activeCustomerList.RemoveRange(MAX_CUSTOMERS,
      _activeCustomerList.Count - MAX_CUSTOMERS);
  }
 
  // 新しいデータを取得します
  var dt = _c1OlapPage.DataSource as CachedDataTable;
  dt.Fill(_activeCustomerList);
}

コードでは、まず、フィールドの Filter を取得してから、フィルタの Apply メソッドを呼び出し、ユーザーが選択した顧客のリストを作成します。範囲チェックの後、不足しているデータを取得する CachedDataTable にリストが渡されます。新しいデータがロードされると、C1OlapPage は通知を受け、自動的にビューを更新します。

アプリケーションを実行する前に、考慮する最後の項目が1つ存在します。フィールドの Filter プロパティは、このフィールドがビューで "アクティブ" である場合に、C1OlapEngine によって考慮されます。"アクティブ" とは、フィールドが RowFieldsColumnFieldsValueFields、または FilterFields コレクションのメンバであることを意味します。この場合、[Customers]フィールドは特殊なフィルタが設定され、常にアクティブである必要があります。このためには、エンジンの Updating イベントを処理して、[Customers]フィールドを常にアクティブにします。

次は、[Customers]フィールドを常にアクティブにするコードです。

コード
コードのコピー
public Form1()
{
    InitializeComponent();
 
    // ** ここは変更しません **
 
    // [Customer]フィールドが常にビュー内にあるかどうかを確認します
    // (このフィールドは、少なくともフィルタとしては必ず使用されるため、この確認が必要です)
    _c1OlapPage.Updating += _c1OlapPage_Updating;
}
 
// [Customer]フィールドが常にビュー内にあるかどうかを確認します
// (このフィールドは、少なくともフィルタとしては必ず使用されるため、この確認が必要です)
void  _c1OlapPage_Updating(object sender, EventArgs e)
{
    var olap = _c1OlapPage.OlapEngine;
    var field = olap.Fields["Customer"];
    if (!field.IsActive)
    {
        olap.FilterFields.Add(field);
    }
}

ここでアプリケーションを実行すると、"Customers" 設定に含まれる顧客のみがビューに含まれていることがわかります。

これは、前に表示された画面に似ています。違いとして、今回はサーバー上でフィルタ処理を実行しています。ほとんどの顧客のデータは、アプリケーションにロードされたことさえありません。

他の顧客を参照するには、[Customer]フィールドを右クリックし、[フィールドの設定]を選択します。次に、以下に示すように、特定の顧客を選択するか、条件を定義してフィルタを編集します。

[OK]をクリックすると、アプリケーションによって変更が検出され、CachingDataTable オブジェクトから追加データが要求されます。新しいデータがロードされると、C1Olap によって変更が検出され、OLAP テーブルが自動的に更新されます。