日期:2010-05-20  浏览次数:20385 次

  摘要:ADO.NET为提高数据密集型(data-intensive)应用程序的性能、简化这类程序的建立过程提供了多种技术。数据集(DataSet)作为ADO.net对象模型的标志,作为一个微型的、不连接(disconnected)的数据源的副本提供服务。虽然使用数据集通过减少对数据库服务器的高花费的访问而提高了性能,但是它也带来了多个用户试图同时访问相同数据的可能性,由此引起数据并发性异常(data concurrency exception)。本文调查了数据并发性异常背后的通常起因,介绍了解决这些问题的技术。

  把数据访问层升级到ADO.NET有很多好处,其中之一是使用内部数据集对象。数据集对象基本上是一个不连接的、内存中的数据库的拷贝。数据集对象包含一个或者多个数据表(DataTable),每个数据表一般对应于数据库中的一个表。数据集提供了很多好处,但也带来一些问题,特别是可能遇到与数据并发性异常相关的问题。我建立了一个简单的Windows Forms顾客服务应用程序,用它来解释该问题的潜在的缺陷。本文我将介绍该应用程序并演示怎样解决它所引起的数据并发性问题。

  本文建立的顾客服务应用程序示例是使用Visual Basic .NET和SQL Server 2000建立的,但是由于微软.NET框架组件是语言无关(language-agnostic)的,因此任何与.NET框架组件兼容的语言都可以使用。同样,由于数据集对象抽象了数据源,数据源的实际执行并不重要;无论下层的数据源是SQL Server、本地XML文件或者从一个服务中检索到的数据,数据并发性异常同样会出现。

  数据集的利弊

  数据集提供了很多好处,例如比起数据库层次,它强化了内存中的完整性规则。数据集对象可以定义和强化表之间的关系和列的约束,确保使用的商业规则对数据库没有缺陷。通过数据库抽象,你能建立单个代码集合访问数据集对象而不必考虑填充该数据集的源数据。下层的数据源也许是SQL Server、Oracle甚至XML文件。无论下层数据源是什么,代码使用相同的方法与数据集交互。这使你能改变下层数据源而不改变代码。

  但是使用数据集的最大好处是提高了性能。因为数据集与下层数据库断开,代码将更少作数据库的调用,显著地提高了性能。你能向数据集的多个数据表中添加新行、验证每行的有效性和参照完整性。数据适配器(DataAdapter)把数据集连接到下层数据库,能使用一条命令更新下层数据库。每个表中的所有新行都使用命令加入,以确保所有添加到数据库的行都是有效的。

  性能的最优化是有代价的。因为数据集对象与下层数据库断开,经常有机会出现数据没有超期(out of date)的情况。因为数据集不保存活动数据,只保存当时填充数据集的活动数据的一个快照,与数据并发性相关的问题就会出现。数据并发性问题出现在多个用户访问相同的数据并且任何一个用户没有其它用户的信息就能更新数据。这就出现了一个用户偶然更新数据而不知道那些数据已经改变了,不是他在程序中看到的了。幸运的是数据集对象拥有捕获数据并发性问题的内建(built-in)支持,因此应用程序能正确地作出反应。

  示例程序

  一个虚拟的公司使用该顾客服务应用程序建立顾客订单,更新顾客的个人信息。有很多客户销售代表(CSR)在桌面上使用该应用程序。CSR使用电话获取订单,从顾客那儿收集个人信息和支付信息。顾客记录保存在数据库中以提高回头客处理订单的速度,CSR接着建立一个订单并把产品项添加上去,指定数量和目前的价格,所有的信息收集后,CSR点击Place Order按钮,向数据库中插入顾客和订单记录。

  CSR也使用应用程序执行通过电子或者缓慢的邮件发送给公司的请求。这些请求在CSR间均匀分开,在每天早晨发送给他们,CSR通过电话执行那些请求。系统设计要提高请求的实现速度,所有的顾客在CSR之间共享。顾客的每个请求,无论通过电话或者邮件,都被不同的CSR处理,增加了发生数据并发性问题的机会。
为了提高性能,应用程序在内存中保持了一个用顾客和订单信息填充的数据集对象。因为很多雇员同时使用该应用程序,就会有许多活动数据的不连接的快照,它们都在雇员的工作站上。所有顾客的维护、订单输入和订单维护都使用名为dsAllData的数据集对象。图1是建立dsAllData的代码,它是全局模块的一部分,因此应用程序中的所有窗体都能使用它。

Const connString = "server=localhost;database=northwind;uid=sa;pwd="
Public connCustSVC As SqlClient.SqlConnection
Public daCustomer As SqlClient.SqlDataAdapter
Public cbCustomer As SqlClient.SqlCommandBuilder
Public daOrders As SqlClient.SqlDataAdapter
Public cbOrders As SqlClient.SqlCommandBuilder
Public daOrderDetail As SqlClient.SqlDataAdapter
Public cbOrderDetail As SqlClient.SqlCommandBuilder
Public dsAllData As DataSet
Public Sub Main()
 connCustSvc = New SqlClient.SqlConnection(connString)
 daCustomer = New SqlClient.SqlDataAdapter("SELECT * FROM Customer", connCustSvc)
 cbCustomer = New SqlClient.SqlCommandBuilder(daCustomer)
 daOrders = New SqlClient.SqlDataAdapter("SELECT * FROM Orders", connCustSvc)
 cbOrders = New SqlClient.SqlCommandBuilder(daOrders)
 daOrderDetail = New SqlClient.SqlDataAdapter("SELECT * FROM OrderDetail", connCustSvc)
 cbOrderDetail = New SqlClient.SqlCommandBuilder(daOrderDetail)
 dsAllData = New DataSet()
 daCustomer.MissingSchemaAction = MissingSchemaAction.AddWithKey
 daCustomer.Fill(dsAllData, "Customer")
 daOrders.MissingSchemaAction = MissingSchemaAction.AddWithKey
 daOrders.Fill(dsAllData, "Orders")
 dsAllData.Tables("Orders").Columns("Total").DefaultValue = 0
 daOrderDetail.MissingSchemaAction = MissingSchemaAction.AddWithKey
 daOrderDetail.Fill(dsAllData, "OrderDetail")
 Application.Run(New frmCustomerMaintenance())
End Sub

  建立dsAllData的代码建立了一个空的数据集对象、三个数据适配器(DataAdapter)和三个命令构造器(CommandBuilder)。每个数据适配器在适当的表上执行一个简单的"SELECT *"操作,而命令构造器用需要的剩余信息填充数据集,使它有插入(insert)、更新(update)和删除(delete)的能力。主程序使用数据适配器对象和所有三个表中的数据填充dsAllData,接着使用Customer Maintenance窗体开始应用程序。
图2显示的是Customer Maintenance屏幕,它有一个绑定到dsAllData的Customers数据表的DataGrid对象。这个简单的表格允许CSR编辑顾客的任意基本属性。因为该表格绑定到了Customers数据表,表格中的任何改变都将自动存储到数据表中。dsAllData将保存这些值,直到CSR点击Save Changes按钮明确地