stand on the shoulders of giants

【ASP.NET Step by Step】之二十一 开放式并发

 

当两个用户同时访问一个页面,一个用户可能更新的是另一个用户已经更改或删除的记录,这就是并发!

并发控制策略

     Ø      什么都不做 –如果并发用户修改的是同一条记录,让最后提交的结果生效(默认的行为) 
     Ø      开放式并发(Optimistic Concurrency - 假定并发冲突只是偶尔发生,绝大多数的时候并不会出现; 那么,当发生一个冲突时,仅仅简单的告知用户,他所作的更改不能保存,因为别的用户已经修改了同一条记录 
     Ø      保守式并发(Pessimistic Concurrency 假定并发冲突经常发生,并且用户不能容忍被告知自己的修改不能保存是由于别人的并发行为;那么,当一个用户开始编辑一条记录,锁定该记录,从而防止其他用户编辑或删除该记录,直到他完成并提交自己的更改

保守式并发较少使用,因为锁定的人离开,其他人就不能更改,使用保守式并发控制的地方,相应地会作一个时间限制,如果到达这个时间限制,则取消锁定。
例如订票网站,当用户完成他的订票过程时会锁定某个特定的座位,这就是一个使用保守式并发控制的例子。

开放式并发原理

当在一个可编辑的GridView里点击编辑按钮时,该记录的值从数据库中读取出来并显示在TextBox和其他Web控件中。这些原始的值保存在GridView里。
随后,当用户完成他的修改并点击更新按钮,这些原始值加上修改后的新值发送到业务逻辑层,然后到数据访问层。
数据访问层必定issue一个SQL语句,它将仅仅更新那些开始编辑时的GridView中原始值数据库中的值一致的记录。

并发控制之有无

使用开放式并发和不使用并发控制的UPDATE DELETE查询之间有什么不同,
可以看看调用DALupdate或者delete时,实际发送到数据库的SQL语法。

 

DELETE FROM [Products] 
    
WHERE (([ProductID] = @Original_ProductID)
    
AND ([ProductName] = @Original_ProductName
    
AND ((@IsNull_SupplierID = 1 AND [SupplierID] IS NULLOR ([SupplierID] = @Original_SupplierID)) 
    
AND ((@IsNull_CategoryID = 1 AND [CategoryID] IS NULLOR ([CategoryID] = @Original_CategoryID)) 
    
AND ((@IsNull_QuantityPerUnit = 1 AND [QuantityPerUnit] IS NULLOR ([QuantityPerUnit] = @Original_QuantityPerUnit)) 
    
AND ((@IsNull_UnitPrice = 1 AND [UnitPrice] IS NULLOR ([UnitPrice] = @Original_UnitPrice)) 
    
AND ((@IsNull_UnitsInStock = 1 AND [UnitsInStock] IS NULLOR ([UnitsInStock] = @Original_UnitsInStock)) 
    
AND ((@IsNull_UnitsOnOrder = 1 AND [UnitsOnOrder] IS NULLOR ([UnitsOnOrder] = @Original_UnitsOnOrder)) 
    
AND ((@IsNull_ReorderLevel = 1 AND [ReorderLevel] IS NULLOR ([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;
}

不使用并发控制的DALProducts 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
{
 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方法;当使用开放式并发时使用这一项

      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        
 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

 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;
            }
        }
    }


 

 

 

 

posted @ 2008-12-23 16:11  DylanWind  阅读(563)  评论(0编辑  收藏  举报