C1Olap可以采纳任何集合作为数据源。它并不受DataTable对象的限制。特别是,它还可以使用LINQ。
LINQ提供易用、高效、自由的模型用于查询数据。它使开发者可以很简单的在客户端应用中创建复杂查询而且不需要修改数据库,例如创建一个新的存储过程。这些查询可以转化为C1Olap数据源,所以最后用户同样可以创建它们自己的数据视图。
为了证明这一观点,创建一个新工程,然后添加一个C1OlapPage 控件到表单中。使用存储过程而不是像我们之前做的那样在designer中设置数据源属性。这一次我们将使用LINQ查询来加载数据,添加下述代码到表单构造函数中实现该效果:
public Form1()
{
// designer
InitializeComponent();
// load all interesting tables into a DataSet var ds = new DataSet(); foreach (string table in
"Products,Categories,Employees," +
"Customers,Orders,Order Details".Split(','))
{
string sql = string.Format("select * from [{0}]", table); var da = new OleDbDataAdapter(sql, GetConnectionString()); da.Fill(ds, table);
}
// build LINQ query and use it as a data source
// for the C1OlapPage control
// ...
}
// get standard nwind mdb connection string static string GetConnectionString()
{
string path =
Environment.GetFolderPath(Environment.SpecialFolder.Personal) +
@"\ComponentOne Samples\Common";
string conn = @"provider=microsoft.jet.oledb.4.0;" +
@"data source={0}\c1nwind.mdb;"; return string.Format(conn, path); }
上述代码从NorthWind数据库中加载了几张表。它假设位于"ComponentOne Samples"文件中的NorthWind数据库是可用的。文件位置由C1Olap安装时设置。如果你在其它地方放置了数据库,你将需要根据情况调整
GetConnectionString方法。
接下来,我们将添加实际的LINQ查询。这是一个很长但是很简单的声明:
// build LINQ query
var q =
from detail in ds.Tables["Order Details"].AsEnumerable() join product in ds.Tables["Products"].AsEnumerable() on detail.Field<int>("ProductID") equals product.Field<int>("ProductID")
join category in ds.Tables["Categories"].AsEnumerable() on product.Field<int>("CategoryID") equals category.Field<int>("CategoryID") join order in ds.Tables["Orders"].AsEnumerable() on detail.Field<int>("OrderID") equals order.Field<int>("OrderID")
join customer in ds.Tables["Customers"].AsEnumerable() on order.Field<string>("CustomerID") equals customer.Field<string>("CustomerID") join employee in ds.Tables["Employees"].AsEnumerable() on order.Field<int>("EmployeeID") equals employee.Field<int>("EmployeeID") select new
{
Sales = (detail.Field<short>("Quantity") *
(double)detail.Field<decimal>("UnitPrice")) *
(1 - (double)detail.Field<float>("Discount")),
OrderDate = order.Field<DateTime>("OrderDate"),
Product = product.Field<string>("ProductName"),
Customer = customer.Field<string>("CompanyName"),
Country = customer.Field<string>("Country"),
Employee = employee.Field<string>("FirstName") + " " + employee.Field<string>("LastName"),
Category = category.Field<string>("CategoryName")
};
// use LINQ query as DataSource for the C1OlapPage control c1OlapPage1.DataSource = q.ToList();
LINQ查询被分为两部分。第一部分使用几个join声明来连接我们从数据库中加载的表。每个表通过将它的主键添加到一个查询中可用字段的方式连接到查询中。我们从"Order Details"表开始,然后连接使用"ProductID"字段连接"Products",使用"CategoryID"字段连接"Categories"。
一旦所有的表都完成连接,你可以创建一个select new声明来创建一个新的匿名类包含我们感兴趣的字段。需要注意的是,这些字段将直接映射到表中的字段,或者它们也许会参与计算。例如,"Sales"字段将参与基于数量,单位价格以及折扣内容的计算。一旦LINQ查询准备就绪,你可以使用LINQ的ToList方法将其转化成一个列表,然后将结果赋值给DataSource属性。 ToList方法是必须的,因为只有它才能启动查询。如果你想简单的将查询赋值给任何控件的DataSource属性,你将会得到一个语法错误提示。
如果你现在运行项目,你将看到它和上一个项目类似,虽然这次我们使用存储过程作为数据源。使用LINQ的优点在于查询可以生成到应用中。你可以在不需要数据库管理员帮助的情况下修改它。
大数据源
到目前为止,所有的例子都在讨论如何加载所有的数据到内存中。这是一个简单而且合适的方式,它在很多情况下都能够很好的工作。
在部分情况下,然而,这里可能有太多的数据以至于无法一次性加载到内存中。设想一下如果一个表中包含一百万甚至更多的行。即使你能够将所有的数据加载到内存中,也将会耗费大量的时间。
你有很多种方法来处理这一问题。你可以创建查询来汇总数据并在服务端缓存这些数据,或者可以使用专业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构造函数使用三个参数:
SqlTemplate
这是一个标准的SQL SELECT声明,使用占位符代替"WHERE"子句。这个声明相当的长,并 且被定义为应用资源。想要了解实际内容,请参阅"SqlFilter"示例。
WhereTemplate
这是一个标准的SQL WHERE声明,其中包含了一个模板。这个模板将被查询中的列表值所 替代。它同样被定义为应用资源,并且包含了下面这条语句:"WHERE Customers.CompanyName in ({0})"
ConnectionString
这个参数包含用于连接数据库的连接字符串。在本例中我们同样适 用 GetConnectionString方法来引用它。该方法将返回一个NorthWind数据库引用(由 C1Olap安装)。现在,数据源已经就绪,我们需要将其连接到C1Olap上来确保:
- 用户可以在C1Olap过滤器看到所有的数据(并不只是当前加载的)并且
- 用户修改过滤器时,新数据将加载以用于显示任何用户需要的客户信息。
想要完成步骤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表。
构建定制用户界面
上个章节中所有的例子都使用了C1OlapPage 控件,控件包含了完整的UI因此只需要很少甚至不需要编写代码。在本章
节中,我们将尝试不使用C1OlapPage创建一个OLAP应用。这将创建一个完整的定制UI,通过使用C1OlapGrid,
C1OlapChart, 和其他标准的 .NET控件实现。
应用完整的源代码可以从"CustomUI"示例中查看。
下图显示应用的设计视图:
在表单的上部有一个面板显示应用标题。在表单右侧安置了一个垂直的工具栏控件,其中包含三组按钮。最上面的分组允许用户选择三种预定义视图中的一种,销售人员的销售额,产品或是国家。下一个分组允许用户在产品价格上应用一个数据库过滤器(昂贵,中等或是廉价)。最后一个按钮提供报表。
报表剩下的区域被一个左侧显示C1OlapGrid 以及右侧显示C1OlapChart 的脚本容器填充。控件将显示当前选中的视图。
表单还包含一个C1OlapPrintDocument 组件,主要用于生成报表。该组件在图片上是不可见的,因为它仅在表单下层的托盘区域显示。C1OlapPrintDocument 组件通过OlapGrid 和OlapChart属性连接到页面上的OLAP控件,这些属性将在设计阶段完成设置。
最后,表单上还有一个C1OlapPanel 控件。它的Visible属性设置为false,因此用户将不会看到它。这个不可见的控件主要用于作为表格和图表的数据源,它将负责数据过滤以及汇总数据。表格和图表都含有它们自己
C1OlapPanel 中的DataSource属性。一旦所有的控件就位,让我们添加代码,连接它们然后让应用开始工作。首先,让我们获取数据,并将其赋值给C1OlapPanel:
private void Form1_Load(object sender, EventArgs e)
{
// load data
var da = new OleDbDataAdapter("select * from Invoices",
GetConnectionString()); var dt = new DataTable(); da.Fill(dt);
// assign it to C1OlapPanel that is driving the app this.c1OlapPanel1.DataSource = dt;
// start with the SalesPerson view, all products
_btnSalesperson.PerformClick();
_btnAllPrices.PerformClick();
}
这些代码使用DataAdapter从NorthWind数据库中获取数据,然后将结果DataTable赋值给
C1OlapPanel.DataSource属性。然后使用PerformClick方法来模拟单击两个按钮,初始化当前视图和过滤器.
选择当前视图按钮事件句柄的代码如下所示:
void _btnSalesperson_Click(object sender, EventArgs e)
{
CheckButton(sender);
BuildView("Salesperson");
}
void _btnProduct_Click(object sender, EventArgs e)
{
CheckButton(sender);
BuildView("ProductName");
}
void _btnCountry_Click(object sender, EventArgs e)
{
CheckButton(sender);
BuildView("Country");
}
所有的句柄都使用BuildView助手方法,该方法如下所示:
// rebuild the view after a button was clicked void BuildView(string fieldName)
{
// get olap engine
var olap = c1OlapPanel1.OlapEngine;
// stop updating until done olap.BeginUpdate();
// format order dates to group by year var f = olap.Fields["OrderDate"];
f.Format = "yyyy";
// clear all fields olap.RowFields.Clear(); olap.ColumnFields.Clear(); olap.ValueFields.Clear();
// build up view
olap.ColumnFields.Add("OrderDate"); olap.RowFields.Add(fieldName); olap.ValueFields.Add("ExtendedPrice");
// restore updates olap.EndUpdate(); }
BuildView方法获取一个C1OlapPanel 对象提供的C1OlapEngine对象,并且立即调用BeginUpdate方法停止更新直到完成了新的视图定义。这样做能提高性能。
代码设置"OrderDate"字段的格式为"yyyy",因此销售额将通过年份来进行分组。清除掉引擎中的RowFields, ColumnFields, 和 ValueFields集合来重新生成视图,然后添加想要显示的字段。调用者传入的"fieldName"参数仅包含一个字段的名称,并且本例中该参数将在视图中被改动。当所有这些都完成之后,代码调用EndUpdate方法,C1OlapPanel将更新输出表。在运行程序之前,让我们看一下实现过滤的代码。事件句柄如下所示:
void _btnExpensive_Click(object sender, EventArgs e)
{
CheckButton(sender);
SetPriceFilter("Expensive Products (price > $50)", 50, double.MaxValue);
}
void _btnModerate_Click(object sender, EventArgs e)
{
CheckButton(sender);
SetPriceFilter("Moderately Priced Products ($20 < price < $50)", 20, 50);
}
void _btnInexpensive_Click(object sender, EventArgs e)
{
CheckButton(sender);
SetPriceFilter("Inexpensive Products (price < $20)", 0, 20);
}
void _btnAllProducts_Click(object sender, EventArgs e)
{
CheckButton(sender);
SetPriceFilter("All Products", 0, double.MaxValue);
}
所有的句柄都使用SetPriceFilter助手方法,该方法如下所示:
// apply a filter to the product price
void SetPriceFilter(string footerText, double min, double max)
{
// get olap engine
var olap = c1OlapPanel1.OlapEngine;
// stop updating until done olap.BeginUpdate();
// make sure unit price field is active in the view
var field = olap.Fields["UnitPrice"]; olap.FilterFields.Add(field);
// customize the filter to apply the condition var filter = field.Filter; filter.Clear();
filter.Condition1.Operator =
C1.Olap.ConditionOperator.GreaterThanOrEqualTo; filter.Condition1.Parameter = min; filter.Condition2.Operator =
C1.Olap.ConditionOperator.LessThanOrEqualTo; filter.Condition2.Parameter = max;
// restore updates olap.EndUpdate();
// set report footer
c1OlapPrintDocument1.FooterText = footerText; }
和之前类似,代码获取到C1OlapEngine的引用,然后立即调用BeginUpdate方法。
随后,它获取到"UnitPrice"字段的引用,这将用于过滤数据。"UnitPrice"字段将添加到引擎的FilterFields 集合中,因此过滤器可以在当前视图中应用。
这是一个非常重要的细节。如果一个字段不存在与任何视图集合(RowFields, ValueFields, FilterFields)中,它就不能包含在视图中,而且它的过滤器属性无法通过任何方式影响视图。
代码接下来将通过设置两个视图中包含值的取值范围,从而对"UnitPrice"字段的过滤器属性进行设置。该范围通过"min"和"max"两个参数来定义。除了使用条件,你还能以列表形式提供将要包含在视图中的数值来实现这一效果。添加条件通常更合适处理数字值和列表,而不是字符串数值和枚举类型。
最后,代码调用EndUpdate方法,然后设置C1OlapPrintDocument的FooterText属性,之后它将可以自动的显示在任何报表中。上述方法使用了另一个叫做CheckButton助手方法,方法如下所示:
// show which button was pressed void CheckButton(object pressedButton)
{
var btn = pressedButton as ToolStripButton; btn.Checked = true;
var items = btn.Owner.Items; var index = items.IndexOf(btn);
for (int i = index + 1; i < items.Count; i++)
{
if (!(items[i] is ToolStripButton)) break; ((ToolStripButton)items[i]).Checked = false;
}
for (int i = index - 1; i > 0 && !(items[i] is ToolStripSeparator); i--)
{
if (!(items[i] is ToolStripButton)) break; ((ToolStripButton)items[i]).Checked = false;
}
}
这一方法让工具栏中的按钮以单选按钮的形式出现。当它们其中一个被点击后,同分组内的其他按钮将失效。
应用几乎完成了。你可以现在就运行它,测试应用的不同视图和过滤能力,就像下图中显示的那样:
这个视图中显示所有产品的销售额,通过月份和国家进行分组。请注意图表中是如何显示接近$300,000的数值。
如果你单击"$$$ Expensive"按钮,过滤器将应用到视图中,你立即就能发现改变。请注意现在图表中是如何显示接近 $80,000的数值。大额数值将占有三分之一的销售额。
应用最后缺少的部分是生成报表。用户已经能够将OlapGrid中的数据复制到Excel中,并且可以打印或者保存这些结果。但是我们可以让它变得更简单,允许他们直接通过应用打印或者创建PDF文件。想要完成这一功能,让我们添加部分代码到句柄中。单击"Reprot..."按钮,代码十分简单:
void _btnReport_Click(object sender, EventArgs e)
{
using (var dlg = new C1.Win.Olap.C1OlapPrintPreviewDialog())
{
dlg.Document = c1OlapPrintDocument1; dlg.StartPosition = FormStartPosition.Manual; dlg.Bounds = this.Bounds; dlg.ShowDialog(this);
}
}
如果你已经在.NET中完成过打印,代码应该与此类似。它首先实例化一个C1OlapPrintPreviewDialog对象。这个类类似于标准的PrintPreviewDialog类,但是它增加了导出PDF文件的能力。
代码中设置对话框的Document属性,初始化它的位置,然后显示对话框。如果你现在运行程序,单击"Report..."按钮,你将会看到一个像下面一样的对话框:
通过这个对话框,用户可以修改页面布局,打印或是导出PDF文件。
代码中配置字段
交互性是Olap应用的一个主要优势。用户应该能够很方便的创建和修改视图,并且迅速看到结果。C1Olap通过它的仿 Excel用户界面,用户友好且简单的对话框很好的实现了这一点。
但是在某些情况下,你业务想要使用代码配置视图。C1Olap通过强大的对象模型,特别是Field类和Filter类来实现这一点。下面的例子介绍了用户如何通过C1Olap创建和配置视图。
首先创建一个新的WinForms应用,然后添加一个C1OlapPage 控件到表单中。切换到代码视图,使用下述代码加载数据,然后将其赋值到C1OlapPage 控件。
public Form1()
{
InitializeComponent();
// get data
var da = new OleDbDataAdapter("select * from invoices", GetConnectionString()); var dt = new DataTable(); da.Fill(dt);
// bind to olap page
this.c1OlapPage1.DataSource = dt;
// build initial view
var olap = this.c1OlapPage1.OlapEngine; olap.ValueFields.Add("ExtendedPrice"); olap.RowFields.Add("ProductName", "OrderDate");
}
static string GetConnectionString()
{
string path = Environment.GetFolderPath(
Environment.SpecialFolder.Personal) +
@"\ComponentOne Samples\Common";
string conn = @"provider=microsoft.jet.oledb.4.0;data source={0}\c1nwind.mdb;"; return string.Format(conn, path); }
代码从NorthWind数据库(由C1Olap安装)中加载了"Invoices"视图,将数据绑定到C1OlapPage控件,然后生成一个初始化视图用于显示根据产品和订单日期统计的"ExtendedPrice"字段的总和。这和上一个示例基本类似。如果你现在运行示例,你将看到一个包含所有的产品和日期的Olap视图。接下来,让我们用C1Olap对象模型来改变订单日期和总价的显示格式:
public Form1()
{
InitializeComponent();
// get data
// no change...
// bind to olap page
// no change...
// build initial view
// no change...
// format order date
var field = olap.Fields["OrderDate"]; field.Format = "yyyy";
// format extended price and change the Subtotal type
// to show the average extended price (instead of sum) field = olap.Fields["ExtendedPrice"]; field.Format = "c";
field.Subtotal = C1.Olap.Subtotal.Average; }
代码将从包含数据源中所有指定字段的Fields集合中检索个别字段。然后将它希望的值赋给Format 和Subtotal属性。Format是常规的.NET字符串,Subtotal决定数值如何聚集显示在Olap视图中。缺省情况下,数值将完成添加,同时其他的聚合统计功能同样可用。这些功能包括求平均值,最大值,最小值,标准偏差以及方差。
现在你可能仅对数据中的子集感兴趣,其中描述了一部分产品和一个年份的数据。用户可以右键单击字段,然后对它们应用过滤器。你同样可以通过代码实现这一功能:
public Form1()
{
InitializeComponent();
// get data
// no changes...
// bind to olap page
// no changes...
// build view
// no changes...
// format order date and extended price
// no changes...
// apply value filter to show only a few products
C1.Olap.C1OlapFilter filter = olap.Fields["ProductName"].Filter; filter.Clear();
filter.ShowValues = "Chai,Chang,Geitost,Ikura".Split(',');
// apply condition filter to show only some dates filter = olap.Fields["OrderDate"].Filter; filter.Clear();
filter.Condition1.Operator =
C1.Olap.ConditionOperator.GreaterThanOrEqualTo; filter.Condition1.Parameter = new DateTime(1996, 1, 1); filter.Condition2.Operator =
C1.Olap.ConditionOperator.LessThanOrEqualTo; filter.Condition2.Parameter = new DateTime(1996, 12, 31); filter.AndConditions = true;
}
代码首先检索关联了"ProductName"字段的C1OlapFilter对象。然后,它清除过滤器,并设置ShowValues属性。这个数据包含一个应该在过滤器中显示的数值数组。在C1Olap中,我们称其为一个"value filter"。
接下来,代码检索关联"OrderDate"字段的过滤器。这一次,我们想要展示指定年份的数值。但是我们并不想列举一整年的每一天。取而代之的是,我们使用一个条件过滤器,通过两个条件来定义它。
第一个条件指定"OrderDate"的数值应该大于等于1996年1月1日。第二个条件用于指定"OrderDate"数值应该小于等于 1996年12月31日。AndConditions属性指定第一以及第二条件如何应用(AND还是OR)。在本例中,我们希望日期同时满足这两个条件,所以AndConditions设置为True。
如果你再次运行项目,你看到的效果应该如下图所示:
<span style="color: #3f529c"><strong>WinForms</strong>版本<strong>OLAP</strong>设计支持文档</span>
下面的章节主要用于描述如何使用OLAP for WinForms在设计环境中配置控件。