日期:2010-05-21  浏览次数:20403 次

   毫无疑问,ADO.NET 向人们提供了一种功能强大、仿真数据库的对象模型,它可以将数据记录保存到内存中。尤其是ADO.net 的 DataSet 类,它不但在功能上相当于数据库表的集中存储器(central repository),而且支持表间的各种约束和逻辑关系。进一步说来,DataSet 对象其实是一种离线数据容器。

    乍一看,只要把 DataSet 类的所有特性联合起来,就能消除 SQL 查询命令中的复杂子句,比如那些泛滥成灾且层层嵌套的 INNER JOIN子句或者 GROUP BY 子句等。复杂的子句可以分解成两个或更多个相互独立的简单子句,而将每个简单子句的查询结果分别保存在不同的 DataTable 对象中;以后只要分析这些内存数据之间的约束和逻辑关系,就能重建原先表之间必要的“参照完整性”(referential integrity)。

    举个例子:你可以把客户(Customers)表与订单(Orders)表分别保存到两个不同的 DataTable 对象中,然后通过 DataRelation 对象进行绑定 (bind) 。这样, SQL Server (或其它 DBMS 系统) 就免除了 INNER JOIN 子句带来的沉重负担;更重要的是,网络传输负荷也因此而大大减轻。象这样简化 SQL 查询的方案固然行之有效,却并不一定总是最佳选择,尤其是当你的数据库规模庞大而且更新频繁时。

    本文将为大家介绍另一种用于简化 SQL 查询的技术,它充分利用 ADO.NET 的内存数据对象减轻了用户和 DBMS 系统的负担。

    分解 SQL 查询命令

    许多有关 ADO.NET 的书籍,比如 David Sceppa 的大作《Programming ADO.NET Core Reference》(微软出版社),都建议把复杂的 SQL 查询命令分解成若干简单的子查询,然后把各个子查询的返回结果分别保存到同一个 DataSet 容器内部的若干个 DataTable 对象中。请看一个实例。

    假设你需要获取一些客户订单信息,要求订单是提交于指定年份而且按客户进行分组,还要求订单中至少包含 30 件商品。同时,你还希望获取每个订单的提交者(employee)名字以及客户(customer)的公司名。你可以用下列 SQL 查询语句来实现它:

DECLARE @TheYear int
SET @TheYear = 1997

SELECT o.customerid, od.orderid, o.orderdate, o.shippeddate,
SUM(od.quantity*od.unitprice) AS price,
c.companyname, e.lastname FROM Orders AS o
INNER JOIN Customers AS c ON c.customerid=o.customerid
INNER JOIN Employees AS e ON e.employeeid=o.employeeid
INNER JOIN [Order Details] AS od ON o.orderid=od.orderid
WHERE Year(o.orderdate) = @TheYear AND od.orderid=o.orderid
GROUP BY o.customerid, c.companyname, od.orderid,
o.orderdate, o.shippeddate, e.lastname
HAVING SUM(od.quantity) >30
ORDER BY o.customerid

    暂且抛开你所用的 ADO 或者 ADO.NET吧。用最原始的命令提交方式执行上述 SQL 查询,可以看到如图 1 所示的结果集:

    图 1. 第一个 SQL 查询命令的输出结果,由 SQL Server Query Analyzer 生成并显示。

    在本次查询中,以一条子句为核心,而另外两条 INNER JOIN 子句起辅助作用。核心子句的功能是从数据库中查询所有提交于指定年份、至少包含 30 件商品的订单。核心子句如下:

SELECT o.customerid, o.orderid, o.orderdate,
o.shippeddate, SUM(od.quantity*od.unitprice) AS price, o.employeeid
FROM orders AS o
INNER JOIN [Order Details] AS od ON o.orderid=od.orderid
WHERE Year(o.orderdate) = @TheYear AND od.orderid=o.orderid
GROUP BY o.customerid, o.orderid, o.orderdate, o.shippeddate,
o.employeeid
HAVING SUM(od.quantity) >30
ORDER BY o.customerid

    在返回结果集中,客户和提交者均用 ID 来表示。然而,本例需要的是客户的公司名(compayname)和提交者的名字(lastname)。末尾的 ORDER BY o.customerid 语句显得特别简单,可是其功能却很重要:由于客户公司名和提交者名字所含的字符较多,使用该语句就能避免它们的重复出现,从而得到更紧凑的结果集。

    综上所述,整个 SQL 查询可以被分解成 3 条子查询命令—— 1 条核心子查询,用于获取订单记录;2 条辅助子查询,用于建立提交者ID - 提交者名字和客户ID - 客户公司名两个对照表,即:

SELECT employeeid, lastname FROM Employees
SELECT customerid, companyname FROM Customers

    以下 ADO.NET 代码演示了如何把这 3 条子查询的返回结果集保存到 DataSet 对象中。

Dim conn As SqlConnection = New SqlConnection(connString)
Dim adapter As SqlDataAdapter = New SqlDataAdapter()

conn.Open()
adapter.SelectCommand = New SqlCommand(cmdCore, conn)
adapter.SelectCommand.Parameters.Add("@TheYear", 1997)
adapter.SelectCommand.Parameters.Add("@TheQuantity", 30)
adapter.Fill(ds, "Orders")
adapter.SelectCommand = New SqlCommand(cmdCust, conn)
adapter.Fill(ds, "Customers")
adapter.SelectCommand = New SqlCommand(cmdEmpl, conn)
adapter.Fill(ds, "Employees")
conn.Close()

    请注意:在连续执行 SQL 查询命令时,你通常都要自行操作数据库连接,以免出现多余的open/close 操作。本例的 adapter.Fill 方法会自动执行 open/close 操作,除非你设置 adapter.SelectCommmand 属性把某个连接显式关联到 adapter 对象之上。

    为了建立内存数据表之间的关系链,你可以创建两个关系,把 employeeid (提交者的ID) 关联到 lastname (提交者的名字),把 customerid (客户