【ASP.NET Step by Step】之二十一 开放式并发
当两个用户同时访问一个页面,一个用户可能更新的是另一个用户已经更改或删除的记录,这就是并发!
并发控制策略
Ø 什么都不做 –如果并发用户修改的是同一条记录,让最后提交的结果生效(默认的行为)
Ø 开放式并发(Optimistic Concurrency) - 假定并发冲突只是偶尔发生,绝大多数的时候并不会出现; 那么,当发生一个冲突时,仅仅简单的告知用户,他所作的更改不能保存,因为别的用户已经修改了同一条记录
Ø 保守式并发(Pessimistic Concurrency) – 假定并发冲突经常发生,并且用户不能容忍被告知自己的修改不能保存是由于别人的并发行为;那么,当一个用户开始编辑一条记录,锁定该记录,从而防止其他用户编辑或删除该记录,直到他完成并提交自己的更改
保守式并发较少使用,因为锁定的人离开,其他人就不能更改,使用保守式并发控制的地方,相应地会作一个时间限制,如果到达这个时间限制,则取消锁定。
例如订票网站,当用户完成他的订票过程时会锁定某个特定的座位,这就是一个使用保守式并发控制的例子。
开放式并发原理
当在一个可编辑的GridView里点击编辑按钮时,该记录的值从数据库中读取出来并显示在TextBox和其他Web控件中。这些原始的值保存在GridView里。
随后,当用户完成他的修改并点击更新按钮,这些原始值加上修改后的新值发送到业务逻辑层,然后到数据访问层。
数据访问层必定issue一个SQL语句,它将仅仅更新那些开始编辑时的GridView中原始值和数据库中的值一致的记录。
并发控制之有无
使用开放式并发和不使用并发控制的UPDATE 和 DELETE查询之间有什么不同,
可以看看调用DAL的update或者delete时,实际发送到数据库的SQL语法。
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))
红色[ProductName]是数据库中值,绿色@Original_ProductID是GridView中原始值。
因为有些可以包含空值,而NULL = NULL则总是返回False(相应地你必须用IS NULL)
this.Adapter.DeleteCommand.Parameters[0].Value = ((int)(Original_ProductID));
...
if ((Original_SupplierID.HasValue == true)) {
this.Adapter.DeleteCommand.Parameters[2].Value = ((object)(0)); //有值就置IsNull_SupplierID为0,
this.Adapter.DeleteCommand.Parameters[3].Value = ((int)(Original_SupplierID.Value));
}
else {
this.Adapter.DeleteCommand.Parameters[2].Value = ((object)(1)); //无值就置为1
this.Adapter.DeleteCommand.Parameters[3].Value = global::System.DBNull.Value;
}
try
{
int returnValue = this.Adapter.DeleteCommand.ExecuteNonQuery();
return returnValue;
}
不使用并发控制的DAL的Products TableAdapter所使用的DELETE语句则简单得多:
相应的.NET DAL Code也简单
this.Adapter.DeleteCommand.Parameters[0].Value = ((int)(Original_ProductID));
global::System.Data.ConnectionState previousConnectionState = this.Adapter.DeleteCommand.Connection.State;
if (((this.Adapter.DeleteCommand.Connection.State & global::System.Data.ConnectionState.Open)
!= global::System.Data.ConnectionState.Open)) {
this.Adapter.DeleteCommand.Connection.Open();
}
try {
int returnValue = this.Adapter.DeleteCommand.ExecuteNonQuery();
return returnValue;
}
finally {
if ((previousConnectionState == global::System.Data.ConnectionState.Closed)) {
this.Adapter.DeleteCommand.Connection.Close();
}
}
}
Update方法之区别
ProductsBLL的Update方法主要有三步:
public bool Updateproduct(string productName, decimal? unitPrice, short? unitsInStock, int productID)
{
//(1) 使用TableAdapter的GetProductByProductID(productID)方法读取当前数据库中的产品信息到ProductRow实例
Northwind.ProductsDataTable products = ProductsAdapter.GetProductByProductID(productID);
if (products.Count == 0)
{
return false;
}
Northwind.ProductsRow product = products[0];
//(2) Assign the new values to the ProductRow instance
product.ProductName = productName;
if (unitPrice == null) product.SetUnitPriceNull(); else product.UnitPrice = unitPrice.Value;
if (unitsInStock == null) product.SetUnitsInStockNull(); else product.UnitsInStock = unitsInStock.Value;
//(3) 调用TableAdapter的Update方法,传入该ProductRow实例
int rowAffected = ProductsAdapter.Update(product);
return rowAffected == 1;
}
相应的ProductsOptimisticConcurrencyBLL类中Update方法:
2 string productName, int? supplierID, int? categoryID, string quantityPerUnit,
3 decimal? unitPrice, short? unitsInStock, short? unitsOnOrder, short? reorderLevel,
4 bool discontinued)
5{
6 product.ProductName = productName;
7 if (supplierID == null) product.SetSupplierIDNull(); else product.SupplierID = supplierID.Value;
8 if (categoryID == null) product.SetCategoryIDNull(); else product.CategoryID = categoryID.Value;
9 if (quantityPerUnit == null) product.SetQuantityPerUnitNull(); else product.QuantityPerUnit = quantityPerUnit;
10 if (unitPrice == null) product.SetUnitPriceNull(); else product.UnitPrice = unitPrice.Value;
11 if (unitsInStock == null) product.SetUnitsInStockNull(); else product.UnitsInStock = unitsInStock.Value;
12 if (unitsOnOrder == null) product.SetUnitsOnOrderNull(); else product.UnitsOnOrder = unitsOnOrder.Value;
13 if (reorderLevel == null) product.SetReorderLevelNull(); else product.ReorderLevel = reorderLevel.Value;
14 product.Discontinued = discontinued;
15}
16
17[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update, true)]
18public bool UpdateProduct(
19 // new parameter values
20 string productName, int? supplierID, int? categoryID, string quantityPerUnit,
21 decimal? unitPrice, short? unitsInStock, short? unitsOnOrder, short? reorderLevel,
22 bool discontinued, int productID,
23
24 // original parameter values
25 string original_productName, int? original_supplierID, int? original_categoryID,
26 string original_quantityPerUnit, decimal? original_unitPrice, short? original_unitsInStock,
27 short? original_unitsOnOrder, short? original_reorderLevel, bool original_discontinued,
28 int original_productID)
29{
30 // STEP 1: Read in the current database product information
31 NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable products = Adapter.GetProductByProductID(original_productID);
32 if (products.Count == 0)
33 // no matching record found, return false
34 return false;
35
36 NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product = products[0];
37
38 // STEP 2: Assign the original values to the product instance
39 AssignAllProductValues(product, original_productName, original_supplierID,
40 original_categoryID, original_quantityPerUnit, original_unitPrice,
41 original_unitsInStock, original_unitsOnOrder, original_reorderLevel,
42 original_discontinued);
43
44 // STEP 3: Accept the changes,
// ProductsOptimisticConcurrencyRow的AcceptChanges()方法 告诉DataRow,现在的值就是Original value
45 product.AcceptChanges();
46
47 // STEP 4: Assign the new values to the product instance
48 AssignAllProductValues(product, productName, supplierID, categoryID, quantityPerUnit, unitPrice,
49 unitsInStock, unitsOnOrder, reorderLevel, discontinued);
50
51 // STEP 5: Update the product record
52 int rowsAffected = Adapter.Update(product);
53
54 // Return true if precisely one row was updated, otherwise false
55 return rowsAffected == 1;
56}
57
注意:
1. ObjectDataSource的OldValuesParameterFormatString属性的值是original_{0}。
然而如果BLL方法的输入参数名为的old_productName,old_supplierID等等,
那么,你不得不把OldValuesParameterFormatString属性的值改为old_{0}。我们的是Origianal_ , 如上所示。
2. 为了ObjectDataSource能够正确地将原始值传送到BLL方法,
还有最后一个属性需要设置。ObjectDataSource有一个ConflictDetection属性,它可以设定为下面的 下面两个值之一:
Ø OverwriteChanges – 默认值; 不将原始值发送到BLL方法相应的输入参数
Ø CompareAllValues – 将原始值发送到BLL方法;当使用开放式并发时使用这一项
一种方法是添加Dummy控件,或者简单的去掉{0:C}
2 <asp:Label ID="DummyUnitPrice" runat="server" Text='<%# Bind("UnitPrice") %>' Visible="false"></asp:Label>
3 <asp:Label ID="Label4" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>'></asp:Label>
4</ItemTemplate>
Update方法也一样,添加Dummy控件
2 <EditItemTemplate>
3
4 </EditItemTemplate>
5 <ItemTemplate>
6 <asp:Label ID="DummyCategoryID" runat="server" Text='<%# Bind("CategoryID") %>' Visible="False"></asp:Label>
7 <asp:Label ID="Label2" runat="server" Text='<%# Eval("CategoryName") %>'></asp:Label>
8 </ItemTemplate>
9</asp:TemplateField>
10<asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
11 <EditItemTemplate>
12
13 </EditItemTemplate>
14 <ItemTemplate>
15 <asp:Label ID="DummySupplierID" runat="server" Text='<%# Bind("SupplierID") %>' Visible="False"></asp:Label>
16 <asp:Label ID="Label3" runat="server" Text='<%# Eval("SupplierName") %>'></asp:Label>
17 </ItemTemplate>
18</asp:TemplateField>
19
捕获ConCurrency
{
if (e.Exception != null && e.Exception.InnerException != null)
{
if (e.Exception.InnerException is System.Data.DBConcurrencyException)
{
// Display the warning message and note that the exception has
// been handled
UpdateConflictMessage.Visible = true;
e.ExceptionHandled = true;
// OPTIONAL: Rebind the data to the GridView and keep it in edit mode
//ProductsGrid.DataBind();
//e.KeepInEditMode = true;
}
}
}
注意,如果是更新时,其他人把这条记录删除,这里不会捕获
因为
// STEP 1: Read in the current database product information
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable products = Adapter.GetProductByProductID(original_productID);
if (products.Count == 0)
// no matching record found, return false
return false;
...
会直接Return false
要用这个来捕获
{
if (e.ReturnValue != null && e.ReturnValue is bool)
{
bool updateReturnValue = (bool)e.ReturnValue;
if (updateReturnValue == false)
{
// No row was updated, display the warning message
UpdateLostMessage.Visible = true;
}
}
}
出处:http://www.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。