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

并发控制策略

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

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

开放式并发原理

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

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

并发控制之有无

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

 

【ASP.NET Step by Step】之二十一 开放式并发DELETE FROM [Products] 【ASP.NET Step by Step】之二十一 开放式并发
【ASP.NET Step by Step】之二十一 开放式并发    
WHERE (([ProductID] = @Original_ProductID)【ASP.NET Step by Step】之二十一 开放式并发
【ASP.NET Step by Step】之二十一 开放式并发    
AND ([ProductName] = @Original_ProductName
【ASP.NET Step by Step】之二十一 开放式并发    
AND ((@IsNull_SupplierID = 1 AND [SupplierID] IS NULLOR ([SupplierID] = @Original_SupplierID)) 【ASP.NET Step by Step】之二十一 开放式并发
【ASP.NET Step by Step】之二十一 开放式并发    
AND ((@IsNull_CategoryID = 1 AND [CategoryID] IS NULLOR ([CategoryID] = @Original_CategoryID)) 【ASP.NET Step by Step】之二十一 开放式并发
【ASP.NET Step by Step】之二十一 开放式并发    
AND ((@IsNull_QuantityPerUnit = 1 AND [QuantityPerUnit] IS NULLOR ([QuantityPerUnit] = @Original_QuantityPerUnit)) 【ASP.NET Step by Step】之二十一 开放式并发
【ASP.NET Step by Step】之二十一 开放式并发    
AND ((@IsNull_UnitPrice = 1 AND [UnitPrice] IS NULLOR ([UnitPrice] = @Original_UnitPrice)) 【ASP.NET Step by Step】之二十一 开放式并发
【ASP.NET Step by Step】之二十一 开放式并发    
AND ((@IsNull_UnitsInStock = 1 AND [UnitsInStock] IS NULLOR ([UnitsInStock] = @Original_UnitsInStock)) 【ASP.NET Step by Step】之二十一 开放式并发
【ASP.NET Step by Step】之二十一 开放式并发    
AND ((@IsNull_UnitsOnOrder = 1 AND [UnitsOnOrder] IS NULLOR ([UnitsOnOrder] = @Original_UnitsOnOrder)) 【ASP.NET Step by Step】之二十一 开放式并发
【ASP.NET Step by Step】之二十一 开放式并发    
AND ((@IsNull_ReorderLevel = 1 AND [ReorderLevel] IS NULLOR ([ReorderLevel] = @Original_ReorderLevel)) 【ASP.NET Step by Step】之二十一 开放式并发
【ASP.NET Step by Step】之二十一 开放式并发    
AND ([Discontinued] = @Original_Discontinued)) 【ASP.NET Step by Step】之二十一 开放式并发

红色[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;
}
【ASP.NET Step by Step】之二十一 开放式并发
try 
{
                
int returnValue = this.Adapter.DeleteCommand.ExecuteNonQuery();
                
return returnValue;
}

不使用并发控制的DALProducts TableAdapter所使用的DELETE语句则简单得多: 

【ASP.NET Step by Step】之二十一 开放式并发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方法:

 1【ASP.NET Step by Step】之二十一 开放式并发protected void AssignAllProductValues(NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product,
 2【ASP.NET Step by Step】之二十一 开放式并发                                      string productName, int? supplierID, int? categoryID, string
 quantityPerUnit,
 3【ASP.NET Step by Step】之二十一 开放式并发                                      decimal? unitPrice, short? unitsInStock, short? unitsOnOrder, short?
 reorderLevel,
 4【ASP.NET Step by Step】之二十一 开放式并发                                      bool
 discontinued)
 5 STEP 3: Accept the changes,
       // ProductsOptimisticConcurrencyRow的AcceptChanges()方法 告诉DataRow,现在的值就是Original value

45【ASP.NET Step by Step】之二十一 开放式并发    product.AcceptChanges();
46
【ASP.NET Step by Step】之二十一 开放式并发
47【ASP.NET Step by Step】之二十一 开放式并发    // STEP 4: Assign the new values to the product instance

48【ASP.NET Step by Step】之二十一 开放式并发    AssignAllProductValues(product, productName, supplierID, categoryID, quantityPerUnit, unitPrice,
49
【ASP.NET Step by Step】之二十一 开放式并发                           unitsInStock, unitsOnOrder, reorderLevel, discontinued);
50
【ASP.NET Step by Step】之二十一 开放式并发    
51【ASP.NET Step by Step】之二十一 开放式并发    // STEP 5: Update the product record

52【ASP.NET Step by Step】之二十一 开放式并发    int rowsAffected = Adapter.Update(product);
53
【ASP.NET Step by Step】之二十一 开放式并发
54【ASP.NET Step by Step】之二十一 开放式并发    // Return true if precisely one row was updated, otherwise false

55【ASP.NET Step by Step】之二十一 开放式并发    return rowsAffected == 1;
56【ASP.NET Step by Step】之二十一 开放式并发}

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


注意:

   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【ASP.NET Step by Step】之二十一 开放式并发<ItemTemplate>
2【ASP.NET Step by Step】之二十一 开放式并发    <asp:Label ID="DummyUnitPrice" runat="server" Text='<%# Bind("UnitPrice") %>' Visible="false"></asp:Label>
3【ASP.NET Step by Step】之二十一 开放式并发    <asp:Label ID="Label4" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>'></asp:Label>
4【ASP.NET Step by Step】之二十一 开放式并发</ItemTemplate>

Update方法也一样,添加Dummy控件

 1【ASP.NET Step by Step】之二十一 开放式并发<asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
 2【ASP.NET Step by Step】之二十一 开放式并发    <EditItemTemplate>
 3【ASP.NET Step by Step】之二十一 开放式并发        【ASP.NET Step by Step】之二十一 开放式并发
 4【ASP.NET Step by Step】之二十一 开放式并发    </EditItemTemplate>
 5【ASP.NET Step by Step】之二十一 开放式并发    <ItemTemplate>
 6【ASP.NET Step by Step】之二十一 开放式并发        <asp:Label ID="DummyCategoryID" runat="server" Text='<%# Bind("CategoryID") %>' Visible="False"></asp:Label>
 7【ASP.NET Step by Step】之二十一 开放式并发        <asp:Label ID="Label2" runat="server" Text='<%# Eval("CategoryName") %>'></asp:Label>
 8【ASP.NET Step by Step】之二十一 开放式并发    </ItemTemplate>
 9【ASP.NET Step by Step】之二十一 开放式并发</asp:TemplateField>
10【ASP.NET Step by Step】之二十一 开放式并发<asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
11【ASP.NET Step by Step】之二十一 开放式并发    <EditItemTemplate>
12【ASP.NET Step by Step】之二十一 开放式并发        【ASP.NET Step by Step】之二十一 开放式并发
13【ASP.NET Step by Step】之二十一 开放式并发    </EditItemTemplate>
14【ASP.NET Step by Step】之二十一 开放式并发    <ItemTemplate>
15【ASP.NET Step by Step】之二十一 开放式并发        <asp:Label ID="DummySupplierID" runat="server" Text='<%# Bind("SupplierID") %>' Visible="False"></asp:Label>
16【ASP.NET Step by Step】之二十一 开放式并发        <asp:Label ID="Label3" runat="server" Text='<%# Eval("SupplierName") %>'></asp:Label>
17【ASP.NET Step by Step】之二十一 开放式并发    </ItemTemplate>
18【ASP.NET Step by Step】之二十一 开放式并发</asp:TemplateField>
19【ASP.NET Step by Step】之二十一 开放式并发

 

捕获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【ASP.NET Step by Step】之二十一 开放式并发
                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;
            }
        }
    }


 

 

 

 

相关文章:

  • 2021-12-15
  • 2021-08-26
  • 2021-12-29
  • 2021-12-17
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-06-30
  • 2021-08-07
  • 2021-10-13
相关资源
相似解决方案