ADO.NET 中的表达式

发布日期: 1/11/2005 | 更新日期: 1/11/2005
*

利用 ADO.NET,使得数据库中关系的管理更为容易。您可以向数据存储返回一系列行集合并在 DataSet 内将它们进行关联,而不是向数据存储返回单个的行集合。在 ADO.NET 中使用相关的 DataTable 具有很多优点,包括能够以层次结构存储数据、更易于更新数据以及能够在列中使用表达式。

本月,我将初步介绍一下 ADO.NET 中的基于列的表达式和计算。我将讨论有关 DataColumn 的聚合函数、汇总计算、执行与整个 DataSet 有关的其他计算以及链接 DataTable 之间的 DataColumn。此外,我还将提供一些实用的示例。

由于有了诸如作为 ANSI SQL 一部分的 SUM、AVG 之类的聚合函数,对 SQL 查询中的一系列相关行进行汇总与求平均对于您来说或许已是过时的玩意了。SQL 还允许对列进行计算,例如将单价乘以定购的产品数量,以获得总价。现在,ADO.NET 提供了一种方式以将这些功能扩展至数据源之外,并在 n 层应用程序的中间层和上层中进行使用。在 ADO.NET 中使用表达式,您可以通过合计 DataSet 内的值、从同一行内的其他值来计算一个值、或者甚至是通过 DataRelation 从父级或子级 DataTable 随意抽取一个值来创建自己的列。通过 ADO.NET 中基于列的表达式和计算,出现了用于管理数据的新技术。

当然,在 ADO.NET 中使用基于表达式的列、聚合以及计算各有千秋。表达式可用于 DataSet 的单个 DataTable 内,或通过 DataRelation 用于 DataSet 的两个 DataTable 对象中。我将对 ADO.NET 和 SQL 中的基于表达式的列之间的差异、以及可以分别从其中获得怎样的结果进行阐述。本专栏中将要讨论的一些操作使用 DataRelation 对象来依赖于关系,这些操作包括利用表达式上滚和下滚。(有关 DataRelation 的详细信息,您可以请参阅 2002 年 11 月的 DataPoint 专栏连载。)本专栏中,我将举例说明如何在 DataColumn 对象中创建表达式、如何利用 DataSet 和 SQL 创建聚合函数、如何在 DataSet 中上滚和下滚字段以及如何在 DataSet 中执行有关列的计算。

SQL 中的表达式

SQL 表达式具有很多形式,可用于多种目的,包括字符串格式设置、用户定义的函数以及数学计算。如果某个 SQL 语句要将第一个名称和最后一个名称连接在一起、计算某个定单的行项目的总价、或在 SQL Server™ 2000 中包含用户定义的函数以检索定单日期,那么该 SQL 语句需包含有一个表达式。

表达式为开发人员提供了极大的灵活性,使他们能以可从数据库中的其他字段导出的行集合来返回值。关系并且甚至有些正常化的数据库不会存储定单的行项目的总价,它将存储单价和数量。可从这些值来计算出总价,因而没有可能数据不会同步。例如,如果您以行来存储数量、单价以及总价,则假设是否有某种方式会使得数据结束时的情况为数量 = 10,单价 = $7.00 以及总价 = $100.00。永远都不会出现这种情况,但是,如果您存储了冗余数据,则有可能出现此情况。适用于事务数据库的通用规则是,不存储任何可以进行导出的信息,例如总价。

此时,SQL 表达式可以发挥作用了。通过以将单价和数量相乘的 SQL 语句创建计算列,可以导出总价(请参阅图 1)。也可通过考虑折扣量(如果有)来计算总价。

以下 SQL 代码显示了如何以 SQL 表达式连接字符串。本示例获取第一个名称和最后一个名称,并将它们放在一起以返回最后一个名称在前的、大写的完整名称:

USE pubs
GO
SELECT au_fname AS FirstName,
       au_lname AS LastName,
       au_lname + ', ' + au_fname AS FullName1,
       (UPPER(au_fname) + ' ' + UPPER(au_lname)) AS FullName2
FROM    authors
ORDER BY
       au_lname,
       au_fname

SQL 表达式可以对字符串设置格式,并在行集合中执行计算,但是在将其用于此目的时仍需要注意一些事情。如果使用图 1中的 SQL 语句填充 DataSet 并且修改了数量列的第一行的值,则表达式列将保持不变。例如,如果数量为 10 且单价为 $7.00,然后将数量更改为 5,则 ExtendedPrice 列的读数将仍然显示为 $70.00 (10 * $7.00) 并且数据不保持同步。主要问题在于,该表达式没有从 SQL 语句移至 ADO.NET DataSet。

DataColumn 表达式

表达式还可以通过 ADO.NET DataColumn 对象进行定义。可定义 DataColumn 以表示总价,而不是通过 SQL 语句计算总价。在 SQL 语句和 DataColumn 中使用表达式的区别之一是,如果表达式中的任何字段受到修改,则 DataColumn 将自动更新定义为表达式字段的 DataColumn,但是它不会更新 SQL 表达式生成的 DataColumn(例如图 1所示的表达式)。

图 2 演示了如何使用 SQL 语句填充 DataSet 中的 DataTable,并使用该 DataSet 的唯一 DataTable 中的其他字段来创建新的 DataColumn 以表示计算表达式。然后,将 DataTable 的默认视图绑定至称为 grdOrderDetail 的 ASP.NET DataGrid。

该代码还利用定单细节信息创建并填充 DataSet。随后,向该 DataSet 的 DataTable 添加列以表示表达式。该列称为 ExtendedPrice,其数据类型被设为类型 decimal,并且其表达式是单价和数量列的结果。该表达式可以引用其 DataTable 的任何 DataColumn。它也从当前为活动状态的 DataRow 获取值。例如,如果第一行的数量为 10,单价为 $7.00,那么总价列将使用这些值来算出 $70.00。

表达式还可以包含文字值以及来自计算中 DataTable 的其他列。例如,可以更改定义总价的表达式以便将折扣考虑进来。带着这个想法,通过更改将 ExtendedPrice 列添加至如下代码行的代码,也可以更改表达式以反映折扣:

oDs.Tables["OrderDetail"].Columns.Add("ExtendedPrice", 
    typeof(decimal), "(UnitPrice * Quantity) * (1 - Discount)");

尝试修改任何 UnitPrice、Discount 或 Quantity DataColumns 的值(不同于从 SQL 表达式导出的列),ExtendedPrice DataColumn 的值也会得到更新。该功能对于用户可以修改其购物车、应用更改以及查看更新的总价的应用程序而言,非常有用。

表达式还可以用于表示其它数据类型,例如布尔值和字符串值。例如,以下代码示例演示了如何添加指示作者是否获取到了折扣的字段。

oDs.Tables["OrderDetail"].Columns.Add("GetsDiscount", typeof(bool),
    "Discount > 0");

您可以通过创建复合表达式来增强之前的表达式,以通过使用 AND、OR 或 NOT 来考虑多个条件。其他运算符也可以用于表达式中,包括诸如 LIKE 和 IN 之类的运算符。

表达式也可以表示字符串值,例如从 DataTable 获取第一个名称列和最后一个名称列将它们放在一起。以下示例代码片段连接了 ProductName 和 ProductID。

oDs.Tables["OrderDetail"].Columns.Add("stringfield", typeof(string),
    "ProductID + '-' + ProductName");

函数

如果您希望列具有包含多个复杂逻辑的表达式,请尝试在其中嵌入某些函数。表达式可以包含诸如 Len、Iif, IsNull、Convert、Trim 以及 Substring 之类的函数。这些函数使得表达式的创建具有更大的灵活性。函数 Len 计算字符串的长度:

oDs.Tables["OrderDetail"].Columns.Add("LengthOfProductName",
    typeof(int), "Len(ProductName)");

函数 Iif 是迭代 If 语句,例如 Visual Basic® .NET 中的的 IIf。它采用三个参数并计算第一个参数以确定其是真还是假。如果第一个参数计算为真,则从 Iif 函数返回第二个参数,否则返回第三个参数。以下是基本的简洁 If...Then...Else 语句,可轻松地将该语句用于嵌入的表达式:

oDs.Tables["OrderDetail"].Columns.Add("Inventory", typeof(string),
    "Iif(Quantity < 10,'A few left', 'Plenty in stock')");

函数 IsNull 计算其第一个参数以查看其是否等于 System.DbNull。如果它计算为假,那么该函数会返回第一个参数的值。如果第一个参数计算为真,那么 IsNull 函数返回第二个参数的值。这可应用于您不希望出现空值并需要利用空字符串或占位符值来替换它们的时候,如下所示:

oDs.Tables["OrderDetail"].Columns.Add("DiscountString",
    typeof(string), "IsNull(Discount, '[null value]')");

函数 Trim 将尾部空白字符从字符串值中移除。函数 Convert 将表达式中值的数据类型更改为传递至 Convert 函数的第二个参数的数据类型。函数 Substring 返回传递至其的一部分字符串值,如它的 T-SQL 副本。Substring 可以用于裁剪长字符串以只显示限定的字符数。它可用于返回字符串的任何部分,并且在必要时可以与其他函数组合。

oDs.Tables["OrderDetail"].Columns.Add("ShortProduct", 
    typeof(string), "Substring(ProductName, 1, 10)");

聚合和关系

表达式内的嵌入函数可有助于创建表示更为复杂逻辑的列。但是,如果您希望创建跨越多个行来计算值的列,情况会怎样?输入聚合函数。

假设您在 DataSet 中具有一个诸如 SQL Server Northwind 数据库中的 Orders-to-Order Details 关系之类的结构,利用包含聚合函数的表达式创建列可以非常漂亮地直接完成此任务。图 3中的代码演示了如何创建 DataSet,在该 DataSet 包含的结构中,定单位于父 DataTable 中而定单细节位于子 DataTable 中。这些 DataTable 对象通过称为 Orders2OrderDetails 的 DataRelation 相互关联。有关 DataRelation 对象以及它们如何帮助在 ADO.NET 中关联层次数据结构和关系数据结构的详细信息,请参阅我撰写的2002 年 11 月 MSDN®Magazine 专栏。

图 3 中,请注意如何创建表达式列并将其添加至 Order DataTable。第一个表达式创建表示每个定单的定单细节项总和的列。特别是,OrderTotal DataColumn 具有一个计算基于 OrderDetail DataTable 的表达式的 DataColumn(称为 ExtendedPrice)总和的表达式。因此,如您所见,您可以在 DataRelation 中使用聚合函数,甚至可以在另一个基于表达式的列中使用它。

ADO.NET 中还提供有其他聚合函数,包括 Sum、Avg、Max、Min、StDev、Var 以及 Count。图 3 中的示例还演示了如何使用 Avg 函数来获取平均定单细节的数量。此处真正关键之一是,DataRelation 中父关键字和子关键字的使用,如下所示:

oDs.Tables["Order"].Columns.Add("AvgQuantity", typeof(decimal),  
    Avg(Child(Order2OrderDetail).Quantity)");

Child 函数接受 DataRelation 的名称以成为子行集合。这个参数是可选的,只在具有多个指示源 DataTable 中子关系的 DataRelation 时才是必需的。因此,如果 DataTable 只具有单个子 DataTable,则可简化语法以排除 DataRelation 的名称,因为它是唯一的 DataRelation:

oDs.Tables["Order"].Columns.Add("AvgQuantity", typeof(decimal), 
    Avg(Child.Quantity)");

下滚和计算

Parent 函数工作原理与 Child 函数相似,当然,除了它将关系链伸至父 DataTable。两种函数都有助于以 ADO.NET 代码创建伪 GROUP BY 功能。

这些关键字使之成为可能的另一个操作是,上滚和下滚未从一个 DataTable 更改至另一个 DataTable 的值。有人经常会问我,如何将一个父和子 DataTable 重新联接到单个 DataTable 中,以便可以用单个的 DataGrid(非层次结构方式)显示它。使用父关键字,可以下滚字段至子 DataTable,然后只将子 DataTable 绑定至 DataGrid。例如,如果我希望在图 3 中示例的 OrderDetail DataTable 的每个行中显示定单日期,则我可以使用 Parent关键字来添加 DataColumn,如:

oDs.Tables["OrderDetail"].Columns.Add("OrderDate", typeof(string), 
    "Parent.OrderDate");

该功能允许您上滚和下滚字段,而无需对它们执行任何计算。通过 Child 关键字使用该滚动功能,您可以从父表格下滚字段并绑定至 DataGrid。通过如此操作,你可以以单个二维行集合结束,就如同您从 SQL 语句获得结果一样。但是,请记住,如果您想在单个行集合中使用该数据,那么您最好不要将单个行集合中的所有数据返回至单个的 DataTable。然而,如果您希望在 DataSet 中使用关系结构,则 Parent 关键字可以为显示数据提供灵活性。

另一个值的一看的功能是 DataTable 的 Compute 函数,该函数通过在当前给定特定筛选器的 DataTable 上使用聚合函数来进行计算。例如,您可以向计算定单总额超过 $999.00 的定单总数的 DataTable 添加列。

在 Compute 函数的第一个参数中,如以下代码所示,通过合计所有符合筛选器要求的 OrderTotal 值来执行一个聚合函数:

//————————————————————————————
//— Display the total number of orders over $999
//————————————————————————————
int iCnt = (int)oDs.Tables["Order"].Compute("Count(OrderTotal)", 
   "OrderTotal >= 1000");
lblTest.Text = iCnt.ToString() + " orders are at least $1000";

该筛选器在第二个参数中指定,并限制聚合函数以只包含符合其条件的行。代码计算其中 OrderTotal 为 $1000.00 或更大值的所有行。对于快速执行有关 DataTable 的计算而言,这是个非常棒的工具,尤其是自从您可以进行筛选后。例如,您可以轻松地查找定购产品 x 的用户数,以及定购产品 y 的用户数,而无需编写循环语句或再次查询数据库。

牢记绑定至表达式的 DataColumn 对象不能进行手动更新非常重要。这些列被绑定至某个表达式,因此无法将它们进行重写,除非该表达式被移除。同样,该数据在充当数据源的数据库(或 XML 文件)中没有相应的列。因此,如果希望将数据保存回数据库,则需要注意表达式将不能被保存至数据库中。

本月我对 SQL 以及 ADO.NET 中的表达式均进行了讨论。既然了解了由表达式提供的宽范围的功能,您会希望在自己的应用程序中使用它们来操纵和添加数据。

通过 mmdata@microsoft.com,将疑问和评论发送至 Johnny。

John Papa 是 MJM Investigations 的一位软件开发 VP,该公司位于北卡罗莱那州的首府罗利。他撰写过几本有关 ADO、XML 和 SQL Server 方面的书籍,并屡屡在行业会议上(如 VSLive)发表演讲。您可以通过 mmdata@microsoft.com 与他联系。

转到原英文页面

posted on 2005-01-26 09:23  Manho  阅读(822)  评论(0编辑  收藏  举报

导航