ComponentOne OLAP for WinForms帮助文档
大数据源

到目前为止,所有的例子都在讨论如何加载所有的数据到内存中。这是一个简单而且合适的方式,它在很多情况下都能够很好的工作。

在部分情况下,然而,这里可能有太多的数据以至于无法一次性加载到内存中。设想一下如果一个表中包含一百万甚至更多的行。即使你能够将所有的数据加载到内存中,也将会耗费大量的时间。

你有很多种方法来处理这一问题。你可以创建查询来汇总数据并在服务端缓存这些数据,或者可以使用专业OLAP数据提供者。无论哪种方式,你最终都将获得可以在C1Olap中使用的表。

但是这里仍然有更简单的方法。假设数据库中包含了数千家公司的信息,但是用户每次仅需要看到一小部分。除了在客户端中依赖C1Olap的数据过滤能力外,你还可以将一部分工作委派给服务端,只加载用户想要看到的公司信息。这个很容易实现,并且不需要任何特殊的软件或者服务端配置。

例如,阅读并思考下面的CachedDataTable类(该类在C1Olap安装的”SqlFilter”例子中):

/// <summary>
/// Extends the <see cref="DataTable"/> class to load and cache
/// data on demand using a <see cref="Fill"/> method that takes 
/// a set of keys as a parameter.
/// </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;
  }
 
  // populate the table by adding any missing values
  public void Fill(IEnumerable filterValues, bool reset)
  {
    // reset table if requested
    if (reset)
    {
      _values.Clear();
      Rows.Clear();
    }
 
    // get a list with the new values
    List<object> newValues = GetNewValues(filterValues);
    if (newValues.Count > 0)
    {
      // get sql statement and data adapter
      var sql = GetSqlStatement(newValues);
      using (var da = new OleDbDataAdapter(sql, ConnectionString))
      {
        // add new values to the table
        int rows = da.Fill(this);
      }
    }
  }
  public void Fill(IEnumerable filterValues)
  {
    Fill(filterValues, false);
  }

该类继承了常规DataTable类,然后提供一个Fill方法用于完全重新注入表数据,或者通过列表的形式将值添加到新的记录。例如,你可以先用两个客户数据(从几千名之中选出)填充表,然后仅在用户需要的时候再添加更多的信息。

需要注意的是上述代码使用了OleDbDataAdapter。这是因为本示例中使用了一个MDB文件作为数据源,还使用了OleDb-style连接字符串。想要在sql server数据源中使用这个类,你需要将OleDbDataAdapter 替换成 SqlDataAdapter.


上方的代码缺少的两个简单方法实现过程如下所示:

 // gets a list with the filter values that are not already in the
  // current values collection; 
  // and add them all to the current values collection.
  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;
  }
 
  // gets a sql statement to add new values to the table
  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;
    }
 
    // build list of values
    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);
      }
    }
 
    // build where clause
    return string.Format(WhereClauseTemplate, sb);
  }
}

GetNewValues方法将以列表的形式返回用户所需且并不存在于当前DataTable中的值。这些值将被添加到表中。

GetSqlStatement方法创建一个新的SQL声明,使用WHERE子句加载用户所需而又没有加载的记录。它使用构造函数调用者提供的字符串模板,这样显得这个类更符合常规。

现在CachedDataTable已经准备就绪,下一步就是使用C1Olap连接它,让用户可以清晰的分析这些数据,就好像它们已经加载到内存中一样。

想要实现这一功能,打开主表单,添加一个 C1OlapPage控件,然后将以下代码添加到表单中:

public partial class Form1 : Form
{
  List<string> _customerList;
  List<string> _activeCustomerList;
  const int MAX_CUSTOMERS = 12;

这些字段将包含一个数据库内所有客户的完整列表,一个用户选中客户列表以及用户一次可选中客户的最大数量。将一次选中客户数量设定一个相对小点的值,从而让用户不能一次性加载太多的数据到应用中。

接下来,我们需要从数据库中获取一个所有客户的列表,让用户选择那些他希望看到的。需要注意的是。这是一个很长但又很紧凑的列表。它只包含客户名称,并没有其他相关细节,如订单,订单详情等等。下面的代码将实现加载所有客户列表功能:

public Form1()
{
  InitializeComponent();
 
  // get complete list of customers
  _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”类型数据。你可以通过右键单击解决方案浏览器中的项目节点,选择”Properties”,然后选择”Settings”标签来创建它: 

 

下面的代码将通过新的设置实现加载活跃客户列表:

// get active customer list
  _activeCustomerList = new List<string>();
  foreach (string customer in Settings.Default.Customers)
  {
    _activeCustomerList.Add(customer);
  }

现在我们已经创建了一个CachedDataTable,并且将其赋值给DataSource属性:

// get data into the CachedDataTable
  var dtSales = new CachedDataTable(
    Resources.SqlTemplate, 
    Resources.WhereTemplate, 
    GetConnectionString());
  dtSales.Fill(_activeCustomerList);
 
  // assign data to C1OlapPage control
  _c1OlapPage.DataSource = dtSales;
 
  // show default view
  var olap = _c1OlapPage.OlapEngine;
  olap.BeginUpdate();
  olap.RowFields.Add("Customer");
  olap.ColumnFields.Add("Category");
  olap.ValueFields.Add("Sales");
  olap.EndUpdate();

CachedDataTable构造函数使用三个参数:

现在,数据源已经就绪,我们需要将其连接到C1Olap上来确保:

  1. 用户可以在C1Olap过滤器看到所有的数据(并不只是当前加载的)并且
  2. 用户修改过滤器时,新数据将加载以用于显示任何用户需要的客户信息。

想要完成步骤1,我们需要将完整客户列表赋值给C1OlapField.Values属性。这个属性包含一个以列表形式显示在过滤器中的客户信息,C1Olap将这些列表值放入到行数据中。在本例中,行数据仅包含一个局部列表,所以我们需要提供完整版本进行替代。

想要完成步骤2,我们需要监听PropertyChanged事件,当用户修改任何字段属性,包括过滤器时将会触发这一事件。当触发后,我们将检索用户选择的客户列表,将列表转移到数据源中。

下述代码将实现该功能:

 // custom filter: customers in the list, customers currently active
  var field = olap.Fields["Customer"];
  var filter = field.Filter;
  filter.Values = _customerList;
  filter.ShowValues = _activeCustomerList.ToArray();
  filter.PropertyChanged += filter_PropertyChanged;

下面是当过滤器改变时更新数据源的事件句柄:

// re-query database when list of selected customers changes
void filter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
  // get reference to parent filter
  var filter = sender as C1.Olap.C1OlapFilter;
 
  // get list of values accepted by the filter
  _activeCustomerList.Clear();
  foreach (string customer in _customerList)
  {
    if (filter.Apply(customer))
    {
      _activeCustomerList.Add(customer);
    }
  }
 
  // skip if no values were selected
  if (_activeCustomerList.Count == 0)
  {
    MessageBox.Show(
      "No customers selected, change will not be applied.",
      "No Customers");
    return;
  }
 
  // trim list if necessary
  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);
  }
 
  // get new data
  var dt = _c1OlapPage.DataSource as CachedDataTable;
  dt.Fill(_activeCustomerList);
}

代码首先检索了字段的过滤器,然后调用字段的Apply方法来创建一个用户选择的客户列表。执行完一些绑定检查后,列表将转移到CachedDataTable表中,在这里将检索是否存在数据丢失。新数据加载后,C1OlapPage将得到通知,并自动刷新视图。

在运行程序之前,完成最后一步。如果字段在视图中处于激活状态,C1OlapEngine将只考虑该字段的Filter属性。这里激活是指字段属于RowFields, ColumnFields, ValueFields 或者 FilterFields集合中的一员。在本例中,”Customers”字段有一个特殊的过滤器,应该一直处于激活状态。为了确保这一点,我们必须调用引擎的Updating事件,确保”Customers”字段一直处于激活状态。

确保”Customers”字段一直处于激活状态的代码如下所示:

public Form1()
{
    InitializeComponent();
 
    // ** no changes here **
 
    // make sure Customer field is always in the view
    // (since it is always used at least as a filter)
    _c1OlapPage.Updating += _c1OlapPage_Updating;
}
 
// make sure Customer field is always in the view
// (since it is always used at least as a filter)
void  _c1OlapPage_Updating(object sender, EventArgs e)
{
    var olap = _c1OlapPage.OlapEngine;
    var field = olap.Fields["Customer"];
    if (!field.IsActive)
    {
        olap.FilterFields.Add(field);
    }
}

如果你现在运行应用,你会注意到只有包含在”Customers”设置中的客户信息才能显示在视图中:

上图和之前看到的类似,不同之处在于这一次的过滤由服务端实现。大多数的客户数据并没有加载到应用中。

想要看到其他客户信息,右键单击”Customers”字段,选择”Field Settings”选项。然后编辑过滤器选择指定客户或者定义一个显示条件。效果如下图所示:

当你单击OK按钮后,应用将检测到改动,然后会从CachingDataTable对象中请求附加数据。一旦新数据加载进来后,C1Olap将自动检测到改动,然后自动更新OLAP表。

 

 


产品网站:http://www.gcpowertools.com.cn 咨询热线:4006576008 ©2015 西安葡萄城