UML软件工程组织

用ADO.NET管理数据库
2003-07-15 陶刚
  使用ADO.NET管理数据库中的关系(relation)非常简单。作为返回单个行集合(rowset)到数据存储(data store)的代替,你可以返回多个行集合并在数据集(DataSet)中将它们关联起来。在ADO.NET中使用关联的数据表(DataTable)有很多好处,包括有将数据存储为层次结构的能力、更容易更新数据、有在列中使用表达式的能力等等。

  本文介绍ADO.NET中基于列(column-based)的表达式和计算。我将演示数据列(DataColumn)上集合函数的使用、汇总、在整个数据集中执行其它类型的计算以及在数据表之间连接数据列。文中还举了几个实例。
  在SQL查询中合计(summing)和平均值(averaging)对你来说很老套了,这要感谢ANSI SQL中有类似SUM 和AVG的集合函数。SQL允许列的计算,例如按次序把产品单价乘以数量产生扩充价格。现在ADO.NET提供了一条途径扩展这些数据源之外的特征并进入n层结构应用程序中间或上层。在ADO.NET中使用列表达式,你能用数据集中的计算值建立自己的列,在同一行中计算其它列的值,甚至通过数据关系(DataRelation)从父数据表或子数据表获取值。使用ADO.NET中的基于列的表达式和计算形成了新的管理数据的技术。

  当然,在ADO.NET中使用基于表达式的列、集合和计算有利也有弊。表达式可用于数据集的单个数据表或者通过数据关系交叉的两个数据表对象。本文将解释在ADO.NET和SQL中基于表达式的列有什么不同,以及你能从它们中得到什么东西。本文讨论了许多操作,包括用表达式上滚(roll up)和下滚(roll down),它依赖于DataRelation对象的关系。我将演示怎样在数据列对象中建立表达式,怎样使用数据集和SQL建立集合函数,怎样在数据集中上滚和下滚字段,怎样在数据集中执行列的计算。

  SQL中的表达式

  SQL表达式为达到不同的目的有多种格式,包括字符串格式、用户定义函数和数学计算。如果SQL语句将姓和名连接到一起、按订单条目次序计算扩充价格、或者在SQL Server 2000中包含一个用户定义函数来检索一个订单日期,SQL语句就包含了表达式。

  表达式为程序开发人员从数据库中的其它字段衍生出的行集合中的返回值提供了很大的灵活性。关系型和标准的数据库不会保存订单条目的扩充价格,它只存储单价和数量。扩充价格能从这些值中计算出来,这样避免了数据不同步。例如,如果在一行中存储了数量、单价和扩充价格,可能出现数量为100,单价为7,扩充价格为100的情况。这种情况不该发生,但是如果存储了冗余数据就可能发生了。事务性数据库的通用准则是不存储可以衍生出的信息,例如扩充价格。

  这样就有了SQL表达式表演的舞台了。扩充价格能通过在SQL语句中建立计算列,将单价乘以数量衍生出。扩充价格又能计算帐户的折扣率等信息。下面的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表达式能够在行集合中格式化字符串并执行计算,但是为了达到这个目的,你要注意一些问题:如果用上面的SQL语句填充数据集,并且首行的数量列被修改了,表达式列不会跟着改变。例如,如果数量为10,价格为7,现在数量变为5,ExtendedPrice列的值仍为70(10×7),数据没有同步。出现这种情况的主要原因是表达式没有从SQL语句传递到ADO.NET数据集。
  数据列表达式

  表达式也可以通过ADO.NET数据列对象定义。作为通过SQL语句计算扩充价格的替代者,数据列可以被定义用于表现扩充价格。在SQL语句和数据列中使用表达式的区别是,如果表达式中的某个字段被修改了,数据列会自动更新表达式所定义的字段,但SQL表达式不会更新数据列。

  下面的代码演示了怎样使用SQL语句填充数据集的数据表并建立一个新数据列描述计算表达式,该表达式使用数据集的唯一数据表中的其它字段得到。接着该数据表的默认视图被绑定到ASP.NET的一个叫做grdOrderDetail 的DataGrid控件。

  这段代码使用订单详细信息建立和填充了一个数据集。接着一个列被添加到该数据集的数据表,用于表现该表达式。该列叫ExtendedPrice,数据类型为浮点型,它的表达式是产品单价和数量列。该表达式能涉及数据表的任意数据列,从当前的数据行中取得值。例如,如果第一行数量为10,单价为7,扩充价格列将使用的值为70。

  在计算中表达式可以包括从数据表中其它字段得到的字面值。例如,定义扩充价格的表达式可以稍作改变,使它考虑折扣率:

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

  试着改变UnitPrice、Discount或Quantity数据列的值,结果是与从SQL表达式衍生的列不同,ExtendedPrice数据列也被更新了。该特征对应用程序很重要,特别是在用户能修改购物车,确认改变,然后查看更新后的总价格的情况下。

  表达式也可以用于表现其它数据类型,例如逻辑型和字符型值。下面的代码演示了添加一个字段用于显示一个作者是否有折扣:

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

  你能使用AND、OR或NOT条件建立混合表达式来考虑多个条件,这样可以加强前面的表达式。还有一些操作符,包括LIKE和IN也可以在表达式中使用。

  表达式也能表现字符串值,例如从数据表中取得姓和名并连接到一起。下面的代码段将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相等。如果计算结果为假,函数返回第一个参数值,如果为真则返回第二个参数值。这在不允许空值,并且希望用空字符串或占位符代替时使用:

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

  Trim函数删除字符串值尾部的空格。Convert函数将表达式中的数据类型转换为函数第二个参数所指的数据类型。Substring函数返回字符串的一部分,可用于将长字符串剪短,只显示定长的字符串,它可以返回字符串的任意部分并根据需要与其它的函数组合使用:

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

  表达式中的嵌入集合函数能帮助你建立一个表现更复杂逻辑的表达式。如果要建立一个列计算跨多个行的值怎么办?最好加入集合函数。

  假定在一个数据集中有类似SQL Server的Northwind数据库中的订单到订单细节的关系结构,那么用包含集合函数的表达式建立列就很直接。下面的代码演示了怎样建立一个包含结构的数据集,在该数据集中订单位于父数据表而订单细节位于子数据表。这些数据表对象通过一个叫Orders2OrderDetails的数据关系彼此关联。

  请注意代码是怎样建立表达式列并添加到Order 数据表的。第一个表达式建立一个表现每个订单的详细信息汇总的列。特别的是OrderTotal数据列有一个表达式用于汇总OrderDetail数据表的基于表达式的ExtendedPrice数据列。你能发现,可以跨越数据关系使用集合函数并将它用在另一个基于表达式的列上。

  ADO.NET中还有其它的集合函数,包括Sum、Avg、Max、Min、StDev、Var和Count。下面的代码演示了怎样使用Avg函数得到订单详细数量的平均值。关键在于使用父(parent)和子(child)关键字通过数据关系连接数据:

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

  Child函数接受数据关系名来获取子行集合。该参数是可选的,只有在源数据表中的子关系多于一个时才需要。因此,如果数据表只有一个子表,语法可以简化:

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

  向下滚动和计算

  Parent函数与Child函数工作类似,只是它沿关系链上行到达父数据表。在ADO.NET代码中这两个函数帮助建立一个假的GROUP BY函数性。

  这些关键字的另一个操作是上滚或下滚从一个数据表到另一个数据表不变的值。经常有人问我怎样将一个父数据表和子数据表连接成一个数据表,这样才能在一个DataGrid控件中显示。使用父关键字可以将字段下滚到子数据表,接着只将该子数据表绑定到DataGrid控件。例如,需要显示OrderDetail数据表中每一行的订单日期,可以添加一个使用父关键字的数据列:

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

  这个特征使得不需要作任何计算就可以上滚和下滚字段。使用Child关键字可以从父表中下滚字段并绑定到DataGrid控件。执行该操作后你得到了一个两位小数的行集合,与从SQL语句中得到的一样。要记住,如果打算使用单个行集合中的数据,最好返回单个行集合到数据表。但是,如果你想在数据集中使用关系结构,Parent关键字给了你显示数据的灵活性。

  另一个值得一看的特征是数据表的Compute函数,该函数使用给定了过虑条件的当前数据表上的集合函数来计算。例如,能给数据表加一列用于计算订单总价大于999的订单总数量。

  在下例所示的Compute函数的第一个参数中,执行了一个集合函数计算符合条件的所有OrderTotal值:

//显示订单价格大于999的订单数量
int iCnt = (int)oDs.Tables["Order"].Compute("Count(OrderTotal)",
"OrderTotal >= 1000");
lblTest.Text = iCnt.ToString() + " orders are at least $1000";

  在第二个参数中指定了过滤器,将集合函数限定为只包含符合条件的行。代码计算OrderTotal大于或等于1000的行。这是一个在数据表中快速执行计算的强大工具,尤其是能使用过虑。例如,你能很容易地查找出订X货物的顾客数量和订Y货物的顾客数量,而不需要循环查询数据库。

  注意绑定到表达式的数据列对象不能手动更新。绑定到表达式的列不会被覆盖,除非表达式被删除。当然,这些数据也不会与数据库或XML文件中作为数据源的列相对应。因此,如果你想将数据保存到数据库中时,要记得表达式不能保存进数据库。

  上文讨论了SQL和ADO.NET中的表达式,演示了表达式所提供的广泛的函数功能,你能在应用程序中使用它们生成和添加数据。


版权所有:UML软件工程组织