您现在的位置是:网站首页> 编程资料编程资料
在ASP.NET 2.0中操作数据之二十一:实现开放式并发_自学过程_
2023-05-24
354人已围观
简介 在ASP.NET 2.0中操作数据之二十一:实现开放式并发_自学过程_
导言
对于那些仅仅允许用户查看数据,或者仅有一个用户可以修改数据的web应用软件,不存在多用户并发冲突的问题。然而对于那些允许多个用户修改或删除数据的web应用软件,则有可能发生一个用户所做的更改与另一个并发用户的更改冲突。在没有任何并发策略的地方,当两个用户同时编辑某一条记录,最后提交的用户的更改将覆盖先提交的用户所作的更改。
例如,假设两个用户,Jisun和Sam,都访问我们的应用软件中的一个页面,这个页面允许访问者通过一个GridView控件更新和删除产品数据。他们都同时点击GridView控件中的Edit按钮。Jisun把产品名称更改为“Chai Tea”并点击Update按钮,实质结果是向数据库发送一个UPDATE语句,它将更新此产品的所有可修改的字段(尽管Jisun实际上只修改了一个字段:ProductName)。
在这一刻,数据库中包含有这条产品记录“Chai Tea”—种类为Beverages、供应商为Exotic Liquids、等该产品的详细信息。然而,在Sam的屏幕中的GridView里,当前编辑行里显示的产片名称依旧是“Chai”。在Jisun的更改被提交后片刻,Sam把种类更改为“Condiments”并点击Update按钮。这个发送到数据库的UPDATE语句的结果是将产品名称更改为“Chai”、CategoryID字段的值是种类Beverages对应的ID,等等。Jisun所作的对产品名称的更改就被覆盖了。图1展示了这些连续的事件。

图 1: 当两个用户同时更新一条记录,则存在一个用户的更改覆盖另一个的更改的可能性
类似地,当两个用户同时访问一个页面,一个用户可能更新的事另一个用户已经删除的记录。或者,在一个用户加载页面跟他点击删除按钮之间的时间里,另一个用户修改了这条记录的内容。
有下面三中并发控制策略可供选择:
1.什么都不做 –如果并发用户修改的是同一条记录,让最后提交的结果生效(默认的行为)
2.开放式并发(Optimistic Concurrency) - 假定并发冲突只是偶尔发生,绝大多数的时候并不会出现; 那么,当发生一个冲突时,仅仅简单的告知用户,他所作的更改不能保存,因为别的用户已经修改了同一条记录
3.保守式并发(Pessimistic Concurrency) – 假定并发冲突经常发生,并且用户不能容忍被告知自己的修改不能保存是由于别人的并发行为;那么,当一个用户开始编辑一条记录,锁定该记录,从而防止其他用户编辑或删除该记录,直到他完成并提交自己的更改
注意:在本节里,我们不讨论保守式并附的例子。保守式并发控制很少使用,因为锁定如果没有完全释放,会妨碍其他用户进行数据更新。例如,如果一个用户为了编辑而锁定某一条记录,但在解锁之前就离开了,那么其他任何用户都不能更新这条记录,直到最初的用户返回并完成他的更新。因此,使用保守式并发控制的地方,相应地会作一个时间限制,如果到达这个时间限制,则取消锁定。例如订票网站,当用户完成他的订票过程时会锁定某个特定的座位,这就是一个使用保守式并发控制的例子。
第一步:如何实现开放式并发控制
开放式并发控制能够确保一条记录在更新或者删除时跟它开始这次更新或修改过程时保持一致。例如,当在一个可编辑的GridView里点击编辑按钮时,该记录的原始值从数据库中读取出来并显示在TextBox和其他Web控件中。这些原始的值保存在GridView里。随后,当用户完成他的修改并点击更新按钮,这些原始值加上修改后的新值发送到业务逻辑层,然后到数据访问层。数据访问层必定发出一个SQL语句,它将仅仅更新那些开始编辑时的原始值根数据库中的值一致的记录。图二描述了这些事件发生的顺序。

图2: 为了更新或删除能够成功,原始值必须与数据库中相应的值一致
有多种方法可以实现开放式并发控制(查看Peter A. Bromberg的文章 Optmistic Concurrency Updating Logic,从摘要中看到许多选择)。ADO.NET类型化数据集提供了一种应用,这只需要在配置时勾选上一个CheckBox。使用开发式并发的目的是使类型化数据集的TableAdapter的UPDATE和DELETE语句可以检测自该记录加载到DataSet中以来数据库中的值是否被更改。例如下面的UPDATE语句,当当前数据库中的值与GridView中开始编辑的原始值一致才更新某个产品的名称和价格。@ProductName 和 @UnitPrice参数包含的是用户输入的新值,而参数@original_ProductName 和 @original_UnitPrice则包含最初点击编辑按钮时加载到GridView中的值:
UPDATE Products SET ProductName = @ProductName, UnitPrice = @UnitPrice WHERE ProductID = @original_ProductID AND ProductName = @original_ProductName AND UnitPrice = @original_UnitPrice
注意:这个UPDATE语句是为了易读而简单化了。实际上,在WHERE子句中检测UnitPrice会比较棘手,这是因为UnitPrice可以包含空值,而NULL = NULL则总是返回False(相应地你必须用IS NULL)。
除了使用一个不同的UPDATE语句之外,配置TableAdapter使用开放式并发控制还需要修改它直接发送到数据库的方法。回到我们的第一节,创建一个数据访问层,这些发送到数据库的方法接收一列标量的值作为输入参数(不仅仅是强类型DataRow或DataTable的实例)。当使用开放式并发,直接发送到数据库的Update() 和 Delete()方法就包含了对应原始值的输入参数。而且,业务逻辑层中批量方式更新的代码(Update()的重载,它不仅接受标量值,也接受DataRows 和 DataTables)也要做出相应的更改。
与其扩展我们现有得数据访问层表适配器使用开放式并发(同时也必须修改业务逻辑层以协调),不如让我们创建一个新的类型化数据集NorthwindOptimisticConcurrency,在它里面我们添加一个使用开放式并发的Products表适配器。然后,我们将在业务逻辑层中创建类ProductsOptimisticConcurrencyBLL,它为了支持开放式并发的DAL会有适当的更改。一旦这些基础工作都已完成,我们就可以创建ASP.NET页面。
第二步: 创建一个支持开放式并发的数据访问层
为了创建一个新的类型化数据集,在App_Code文件夹里的DAL文件夹上右键点击,选择添加一个新的数据集并命名为NorthwindOptimisticConcurrency。正如我们在第一节中看到过的那样,系统会自动添加一个表适配器(TableAdapter)到当前的类型化数据集众,并自动地进入TableAdapter配置向导。在第一屏中,向导提示我们选择数据库连接 – 连接到同样的数据库Northwind并使用Web.config里设置好的连接字符串NORTHWNDConnectionString。

图 3: 连接到同一个数据库Northwind
下一步,向导提示我们选择如何访问数据库:通过一个指定的SQL语句,创建新的存储过程,或者使用一个现有的存储过程。既然我们最初的DAL是使用的是指定SQL查询语句,这里我们还是使用它。

图4: 使用指定SQL语句的方式访问数据库
下一步,进入查询分析器,返回产品信息。让我们使用在最初的DAL中产品TableAdapter相同的SQL查询,它返回产品的所有字段包括产品的供应商和类别名称。
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) as CategoryName, (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName FROM Products

图5:使用在最初的DAL中产品TableAdapter相同的SQL查询
在我们进入下一步之前,点击“高级选项”按钮。要让这个TableAdapter使用开放式并发,仅仅需要勾选上“使用开放式并发”。

图6:勾选“使用开放式并发”启用开放式并发控制
最后,需要指出的是,该TableAdapter应该同时使用“填充DataTable”和“返回DataTable”两种要生成的方法;并且,勾选“创建方法以将更新直接发送到数据库(GenerateDBDirectMethods)”。将返回DataTable的方法名称从GetData改为GetProducts,使之与我们最初的DAL中的命名规则匹配。

图7:让这个TableAdapter利用所有的数据访问方式
完成了配置向导后,该数据集设计器将包含一个强类型的Products DataTable和TableAdapter。让我们花些时间把该DataTable的名称Products改为ProductsOptimisticConcurrency,方法是右键点击DataTable的标题栏,从菜单中选择“重命名”。

图8:一个DataTable和TableAdapter已经添加到类型化数据集
为了看看ProductsOptimisticConcurrency TableAdapter(使用开放式并发)和Products TableAdapter(不使用并发控制)的UPDATE 和 DELETE查询之间有什么不同,选中该TableAdapter并转到属性窗口。在DeleteCommand 和 UpdateCommand 这两个属性的 CommandText 子属性里,我们可以看到调用DAL的update或者delete关联的方法时发送到数据库的实际的SQL语法。ProductsOptimisticConcurrency TableAdapter使用的DELETE语句是
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID) AND ([ProductName] = @Original_ProductName) AND ((@IsNull_SupplierID = 1 AND [SupplierID] IS NULL) OR ([SupplierID] = @Original_SupplierID)) AND ((@IsNull_CategoryID = 1 AND [CategoryID] IS NULL) OR ([CategoryID] = @Original_CategoryID)) AND ((@IsNull_QuantityPerUnit = 1 AND [QuantityPerUnit] IS NULL) OR ([QuantityPerUnit] = @Original_QuantityPerUnit)) AND ((@IsNull_UnitPrice = 1 AND [UnitPrice] IS NULL) OR ([UnitPrice] = @Original_UnitPrice)) AND ((@IsNull_UnitsInStock = 1 AND [UnitsInStock] IS NULL) OR ([UnitsInStock] = @Original_UnitsInStock)) AND ((@IsNull_UnitsOnOrder = 1 AND [UnitsOnOrder] IS NULL) OR ([UnitsOnOrder] = @Original_UnitsOnOrder)) AND ((@IsNull_ReorderLevel = 1 AND [ReorderLevel] IS NULL) OR ([ReorderLevel] = @Original_ReorderLevel)) AND ([Discontinued] = @Original_Discontinued))
相反,最初的DAL的Products TableAdapter所使用的DELETE语句则简单得多:
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))
正如你所看到的,启用了开发式并发的TableAdapter所使用的DELETE语句里的WHERE子句包含了对表Product每一个字段现有的值与GridView(或者DetailsView,FormView)最后一次加载时的原始值的对比。因为除了ProductID,ProductName, 和Discontinued之外,其他所有字段都可能为NULL值,所以WHERE子句里还包含了额外的参数以及与NULL值恰当的比较。
在这一节里,我们不会在启用了开放式并发的数据集里增加其他的DataTable了,因为我们的ASP.NET页面将仅提供更新和删除产品信息的功能。然而,我们仍然需要在ProductsOptimisticConcurrency TableAdapter里添加GetProductByProductID(productID) 方法。
为了实现这一点,在TableAdapter的标题栏(在Fill和GetProducts方法名的上方)上右键并从菜单里选择“添加查询”。这将启动TableAdapter查询配置向导。在TableAdapter的最初配置的基础上,选择指定SQL语句来创建GetProductByProductID(productID)方法(见图四)。因为GetProductByProductID(productID)方法返回指定产品的信息,因此需要指定SQL查询类型为“SELECT(返回行)”。

图9:标记SQL查询类型为“SELECT(返回行)”
进入下一步,向导提示我们指定SQL语句,并且与载入TableAdapter默认查询语句。在现有的查询语句的基础上添加WHERE ProductID = @ProductID子句,如图10:

图10:在预载入的查询语句上添加WHERE子句从而返回特定的产品记录
最后,把生成的方法重命名为FillByProductID和GetProductByProductID。

图11:把生成的方法重命名为FillByProductID和GetProductByProductID
完成这个向导之后,现在这个TableAdapter包含两个访问数据的方法:GetProducts(),它返回所有 的产品;和GetProductByProductID(productID),它返回特定的产品。
第三步: 创建一个支持启用了开放式并发的DAL的业务逻辑层
我们现有的ProductsBLL类包含批量更新和直接发送数据库的模式的例子。AddProduct方法和 UpdateProduct重载都使用了批量更新模式,通过一个ProductRow实例发送到TableAdapter的Update方法。另一方面,DeleteProduct方法则使用直接发送到数据库的模式,调用TableAdapter的Delete(productID)方法。在新的ProductsOptimisticConcurrency TableAdapter里,发送到数据库的方法现还要求传入原始的值。例如,Delete方法
现在要求十个输入参数:原始的ProductID、ProductName、SupplierID、CategoryID、QuantityPerUnit、UnitPrice、UnitsInStock、UnitsOnOrder、ReorderLevel和Discontinued。它在发送到数据库的DELETE语句的WHERE子句里使用这些额外的输入参数,仅仅删除当前数据库的值与原始值一致的指定记录。
使用批量更新模式时,如果标记给TableAdapter的Update使用的方法没有更改,那么代码就需要同时记录原始值和新的值。然而,与其在我们现有的ProductsBLL类的基础上试图使用启用了开放式并发的DAL,不如让我们重新创意一个业务逻辑类支持我们新的DAL。在App_Code文件夹下的BLL子文件夹里,添加一个名为ProductsOptimisticConcurrencyBLL的新类。

图 12: 添加ProductsOptimisticConcurrencyBLL类到BLL文件夹
然后,在ProductsOptimisticConcurrencyBLL类里添加如下代码:
提示:
本文由神整理自网络,如有侵权请联系本站删除!
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!
相关内容
- 在ASP.NET 2.0中操作数据之二十:定制数据修改界面_自学过程_
- 在ASP.NET 2.0中操作数据之十九:给编辑和新增界面增加验证控件_自学过程_
- 在ASP.NET 2.0中操作数据之十八:在ASP.NET页面中处理BLL/DAL层的异常_自学过程_
- 在ASP.NET 2.0中操作数据之十七:研究插入、更新和删除的关联事件_自学过程_
- 在ASP.NET 2.0中操作数据之十六:概述插入、更新和删除数据_自学过程_
- 在ASP.NET 2.0中操作数据之十五:在GridView的页脚中显示统计信息_自学过程_
- 在ASP.NET 2.0中操作数据之十四:使用FormView 的模板_自学过程_
- ASP.NET MVC使用RazorEngine解析模板生成静态页_实用技巧_
- 在ASP.NET 2.0中操作数据之十三:在DetailsView控件中使用TemplateField_自学过程_
- 在ASP.NET 2.0中操作数据之十二:在GridView控件中使用TemplateField_自学过程_
点击排行
本栏推荐
