【ASP.NET Step by Step】之二十一 开放式并发
2008-12-23 16:11
309 查看
当两个用户同时访问一个页面,一个用户可能更新的是另一个用户已经更改或删除的记录,这就是并发!
Ø 开放式并发(Optimistic Concurrency) - 假定并发冲突只是偶尔发生,绝大多数的时候并不会出现; 那么,当发生一个冲突时,仅仅简单的告知用户,他所作的更改不能保存,因为别的用户已经修改了同一条记录
Ø 保守式并发(Pessimistic Concurrency) – 假定并发冲突经常发生,并且用户不能容忍被告知自己的修改不能保存是由于别人的并发行为;那么,当一个用户开始编辑一条记录,锁定该记录,从而防止其他用户编辑或删除该记录,直到他完成并提交自己的更改
保守式并发较少使用,因为锁定的人离开,其他人就不能更改,使用保守式并发控制的地方,相应地会作一个时间限制,如果到达这个时间限制,则取消锁定。
例如订票网站,当用户完成他的订票过程时会锁定某个特定的座位,这就是一个使用保守式并发控制的例子。
随后,当用户完成他的修改并点击更新按钮,这些原始值加上修改后的新值发送到业务逻辑层,然后到数据访问层。
数据访问层必定issue一个SQL语句,它将仅仅更新那些开始编辑时的GridView中原始值和数据库中的值一致的记录。
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))
红色[ProductName]是数据库中值,绿色@Original_ProductID是GridView中原始值。
因为有些可以包含空值,而NULL = NULL则总是返回False(相应地你必须用IS NULL)
.NET创建的DAL code,
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语句则简单得多:
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))
相应的.NET DAL Code也简单
public virtual int Delete(int Original_ProductID) {
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();
}
}
}
[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update, false)]
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方法:
1protected void AssignAllProductValues(NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product,
2 string productName, int? supplierID, int? categoryID, string quantityPerUnit,
3 decimal? unitPrice, short? unitsInStock, short? unitsOnOrder, short? reorderLevel,
4 bool discontinued)
5
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
注意:
1. ObjectDataSource的OldValuesParameterFormatString属性的值是original_{0}。
然而如果BLL方法的输入参数名为的old_productName,old_supplierID等等,
那么,你不得不把OldValuesParameterFormatString属性的值改为old_{0}。我们的是Origianal_ , 如上所示。
2. 为了ObjectDataSource能够正确地将原始值传送到BLL方法,
还有最后一个属性需要设置。ObjectDataSource有一个ConflictDetection属性,它可以设定为下面的 下面两个值之一:
Ø OverwriteChanges – 默认值; 不将原始值发送到BLL方法相应的输入参数
Ø CompareAllValues – 将原始值发送到BLL方法;当使用开放式并发时使用这一项
3. 因为Delete方法不仅仅接受ProductID参数,所以,会遇到一些错误
一种方法是添加Dummy控件,或者简单的去掉{0:C}
1<ItemTemplate>
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控件
1<asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
2 <EditItemTemplate>
3 </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 </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
{
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
要用这个来捕获
protected void ProductsOptimisticConcurrencyDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
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;
}
}
}
并发控制策略
Ø 什么都不做 –如果并发用户修改的是同一条记录,让最后提交的结果生效(默认的行为)Ø 开放式并发(Optimistic Concurrency) - 假定并发冲突只是偶尔发生,绝大多数的时候并不会出现; 那么,当发生一个冲突时,仅仅简单的告知用户,他所作的更改不能保存,因为别的用户已经修改了同一条记录
Ø 保守式并发(Pessimistic Concurrency) – 假定并发冲突经常发生,并且用户不能容忍被告知自己的修改不能保存是由于别人的并发行为;那么,当一个用户开始编辑一条记录,锁定该记录,从而防止其他用户编辑或删除该记录,直到他完成并提交自己的更改
保守式并发较少使用,因为锁定的人离开,其他人就不能更改,使用保守式并发控制的地方,相应地会作一个时间限制,如果到达这个时间限制,则取消锁定。
例如订票网站,当用户完成他的订票过程时会锁定某个特定的座位,这就是一个使用保守式并发控制的例子。
开放式并发原理
当在一个可编辑的GridView里点击编辑按钮时,该记录的值从数据库中读取出来并显示在TextBox和其他Web控件中。这些原始的值保存在GridView里。随后,当用户完成他的修改并点击更新按钮,这些原始值加上修改后的新值发送到业务逻辑层,然后到数据访问层。
数据访问层必定issue一个SQL语句,它将仅仅更新那些开始编辑时的GridView中原始值和数据库中的值一致的记录。
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))
红色[ProductName]是数据库中值,绿色@Original_ProductID是GridView中原始值。
因为有些可以包含空值,而NULL = NULL则总是返回False(相应地你必须用IS NULL)
.NET创建的DAL code,
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语句则简单得多:
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))
相应的.NET DAL Code也简单
public virtual int Delete(int Original_ProductID) {
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方法主要有三步:[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update, false)]
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方法:
1protected void AssignAllProductValues(NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product,
2 string productName, int? supplierID, int? categoryID, string quantityPerUnit,
3 decimal? unitPrice, short? unitsInStock, short? unitsOnOrder, short? reorderLevel,
4 bool discontinued)
5
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
注意:
1. ObjectDataSource的OldValuesParameterFormatString属性的值是original_{0}。
然而如果BLL方法的输入参数名为的old_productName,old_supplierID等等,
那么,你不得不把OldValuesParameterFormatString属性的值改为old_{0}。我们的是Origianal_ , 如上所示。
2. 为了ObjectDataSource能够正确地将原始值传送到BLL方法,
还有最后一个属性需要设置。ObjectDataSource有一个ConflictDetection属性,它可以设定为下面的 下面两个值之一:
Ø OverwriteChanges – 默认值; 不将原始值发送到BLL方法相应的输入参数
Ø CompareAllValues – 将原始值发送到BLL方法;当使用开放式并发时使用这一项
3. 因为Delete方法不仅仅接受ProductID参数,所以,会遇到一些错误
一种方法是添加Dummy控件,或者简单的去掉{0:C}
1<ItemTemplate>
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控件
1<asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
2 <EditItemTemplate>
3 </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 </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
protected void ProductsGrid_RowUpdated(object sender, GridViewUpdatedEventArgs e){
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
要用这个来捕获
protected void ProductsOptimisticConcurrencyDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
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;
}
}
}
相关文章推荐
- Scott Mitchell 的ASP.NET 2.0数据教程之二十一:: 实现开放式并发
- 在ASP.NET 2.0中操作数据之二十一:实现开放式并发
- Scott Mitchell 的ASP.NET 2.0数据教程之二十一:实现开放式并发
- Scott Mitchell 的ASP.NET 2.0数据教程之二十一:: 实现开放式并发
- Scott Mitchell 的ASP.NET 2.0数据教程之二十一:: 实现开放式并发
- 在ASP.NET 2.0中操作数据之二十一:实现开放式并发
- Scott Mitchell 的ASP.NET 2.0数据教程之二十一:: 实现开放式并发
- Scott Mitchell 的ASP.NET 2.0数据教程之二十一: 实现开放式并发
- Scott Mitchell 的ASP.NET 2.0数据教程之二十一:: 实现开放式并发
- 【ASP.NET Step by Step】之十一至十五 Custom Formatting Based Upon Data
- ASP.NET组件设计Step by Step(1)
- ASP.NET组件设计Step by Step(8)
- ASP.NET MVC Step by Step中文版
- 【ASP.NET Step by Step】之六 Programmatically Setting the ObjectDataSource's Parameter Values
- 【Step by Step】编写代码验证一个ASP.NET应用程序和页面的生命周期
- 【Azure Services Platform Step by Step-第14篇】使用Azure版的ASP.NET Application Providers
- ASP.NET组件编程step by step
- ASP.NET组件设计Step by Step(3)
- ASP.NET组件设计Step by Step(7)
- Microsoft ASP.NET 4 Step by Step