中文字幕人妻中文_99精品欧美一区二区三区综合在线_精品久久久久一区二区_色月丁香_免费福利在线视频_欧美大片免费观看网址_国产伦精品一区二区三区在线播放_污污污污污污www网站免费_久久月本道色综合久久_色69激情爱久久_尹人香蕉久久99天天拍_国产美女www_亚洲国产精品无码7777一线_五月婷婷六月激情_看免费一级片_精品久久久久久成人av_在线色亚洲_女人另类性混交zo_国产精品青青在线观看爽香蕉_人人澡人人添人人爽一区二区

主頁 > 知識庫 > 在ASP.NET 2.0中操作數據之二十一:實現開放式并發

在ASP.NET 2.0中操作數據之二十一:實現開放式并發

熱門標簽:西寧呼叫中心外呼系統線路商 外呼電話機器人成本 網絡電話外呼系統上海 百應電話機器人外呼系統 聯通官網400電話辦理 臨沂智能電話機器人加盟 400電話辦理怎么樣 蘇州如何辦理400電話 地圖標注軟件免費下載

導言

  對于那些僅僅允許用戶查看數據,或者僅有一個用戶可以修改數據的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類里添加如下代碼:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindOptimisticConcurrencyTableAdapters;
[System.ComponentModel.DataObject]
public class ProductsOptimisticConcurrencyBLL
{
  private ProductsOptimisticConcurrencyTableAdapter _productsAdapter = null;
  protected ProductsOptimisticConcurrencyTableAdapter Adapter
  {
    get
    {
      if (_productsAdapter == null)
        _productsAdapter = new ProductsOptimisticConcurrencyTableAdapter();
      return _productsAdapter;
    }
  }
  [System.ComponentModel.DataObjectMethodAttribute
  (System.ComponentModel.DataObjectMethodType.Select, true)]
  public NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable GetProducts()
  {
    return Adapter.GetProducts();
  }
}

  注意在類的聲明開始之前的using NorthwindOptimisticConcurrencyTableAdapters語句。命名空間NorthwindOptimisticConcurrencyTableAdapters包含了類ProductsOptimisticConcurrencyTableAdapter,它提供DAL的方法。并且,在類聲明之前我們還能找到System.ComponentModel.DataObject屬性標志,它指示Visual Studio把該類包含在ObjectDataSource向導的數據對象下拉列表中。

  類ProductsOptimisticConcurrencyBLL的Adapter屬性提供快速訪問ProductsOptimisticConcurrencyTableAdapter類的一個實例,并和我們最初的BLL類(ProductsBLL、CategoriesBLL等等)相似。最后,方法GetProducts()僅僅是調用DAL的GetProdcuts()方法并返回一個ProductsOptimisticConcurrencyDataTable對象,該對象由對應數據庫里每一個產品記錄的ProductsOptimisticConcurrencyRow實例組成。

  使用支持開放式并發的發送到數據庫的模式刪除一個產品記錄

  當使用支持開放式并發的DAL發送到數據庫的模式,方法必須傳入新值和原始值。對刪除來說,這沒有新的值,所以僅僅需要傳入原始值。那么,在我們的BLL里,我們必須接收所有原始值所為輸入參數。讓ProductsOptimisticConcurrencyBLL類的DeleteProduct方法使用這個發送到數據的方法。這意味著此方法必須接受所有的十個產品數據字段作為輸入參數,并傳送這些參數到DAL,如下面的代碼所示:

[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Delete, true)]
public bool DeleteProduct
  (int original_productID, string original_productName,
  int? original_supplierID, int? original_categoryID,
  string original_quantityPerUnit, decimal? original_unitPrice,
  short? original_unitsInStock, short? original_unitsOnOrder,
  short? original_reorderLevel, bool original_discontinued)
{
  int rowsAffected = Adapter.Delete(original_productID,
                   original_productName,
                   original_supplierID,
                   original_categoryID,
                   original_quantityPerUnit,
                   original_unitPrice,
                   original_unitsInStock,
                   original_unitsOnOrder,
                   original_reorderLevel,
                   original_discontinued);
  // Return true if precisely one row was deleted, otherwise false
  return rowsAffected == 1;
}

  如果這些在GridView(或者是DetailsView、FormView)最后一次加載時的原始值跟用戶點擊刪除按鈕時數據庫中的值不一致, WHERE子句將不能匹配任何數據庫記錄,這就沒有記錄會受到影響。因此,TableAdapter的Delete方法將返回0并且BLL的DeleteProduct方法返回false。

  使用支持開放式并發的批量更新模式修改一個產品記錄

  正如之前注意到的,批量更新模式時用的TableAdapter的Update方法也有同樣的方法聲明為不管是否支持開放式并發。也就是,Update方法可以接受一個DataRow,一批DataRow,一個DataTable,或者一個類型化的數據集。正是因為DataTable在它的DataRow(s)里保留了從原始值到修改后的值這個變化的軌跡使這成為可能。當DAL生成它的UPDATE語句時,參數@original_ColumnName裝入DataRow中的原始值,反之,參數@ColumnName裝入DataRow中修改后的值。

  在類ProductsBLL(我們最初使用的不支持開放式并發DAL的)里,當我們使用批量更新模式更新產品信息時,我們的代碼執行的則是按順序執行下列事件:

1.使用TableAdapter的GetProductByProductID(productID)方法讀取當前數據庫中的產品信息到ProductRow實例
2.在第1步里將新的值賦值到ProductRow實例
3.調用TableAdapter的Update方法,傳入該ProductRow實例

  這一連串的步驟,無論如何都不可能支持開放式并發,因為在第一步中產生的ProductRow是直接從數據庫組裝的,這意味著,DataRow中使用的原始值是當前存在于數據庫中值,而并非開始編輯過程時綁定到GridView的值。相反地,當使用啟用開放式并發的DAL,我們需要修改UpdateProduct方法的重載以使用下面這些步驟:

1.使用TableAdapter的GetProductByProductID(productID)方法讀取當前數據庫中的產品信息到ProductsOptimisticConcurrencyRow實例
2.在第1步里將原始 值賦值到ProductsOptimisticConcurrencyRow實例
3.調用ProductsOptimisticConcurrencyRow實例的AcceptChanges()方法,這指示DataRow目前這些值是“原始”的值
4.將新 的值賦值到ProductsOptimisticConcurrencyRow實例
5.調用TableAdapter的Update方法,傳入該ProductsOptimisticConcurrencyRow實例

  第1步讀取當前數據庫里指定產品記錄的所有字段的值。對更新所有 產品字段的UpdateProduct的重載里,這一步是多余的(因為這些值在第2步中被改寫),而對那些僅僅傳入部分字段值的重載方法來說則是必要的。一旦原始值賦值到ProductsOptimisticConcurrencyRow實例,調用AcceptChanges()方法,這將當前DataRow中的值標記為原始值,這些值將用作UPDATE語句的@original_ColumnNam參數。然后,新的參數值被賦值到ProductsOptimisticConcurrencyRow,最后,調用Update方法,傳入這個DataRow。

  下面這些代碼展示了重載方法UpdateProduct接受所有產品數據字段作為輸入參數。雖然這里沒有展示,實際上從本節教程下載的ProductsOptimisticConcurrencyBLL類里還包含了重載方法UpdateProduct,它僅僅接受產品名稱和單價作為輸入參數。

protected void AssignAllProductValues
  (NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product,
  string productName, int? supplierID, int? categoryID, string quantityPerUnit,
  decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
  short? reorderLevel, bool discontinued)
{
  product.ProductName = productName;
  if (supplierID == null)
    product.SetSupplierIDNull();
  else
    product.SupplierID = supplierID.Value;
  if (categoryID == null)
    product.SetCategoryIDNull();
  else
    product.CategoryID = categoryID.Value;
  if (quantityPerUnit == null)
    product.SetQuantityPerUnitNull();
  else
    product.QuantityPerUnit = quantityPerUnit;
  if (unitPrice == null)
    product.SetUnitPriceNull();
  else
    product.UnitPrice = unitPrice.Value;
  if (unitsInStock == null)
    product.SetUnitsInStockNull();
  else
    product.UnitsInStock = unitsInStock.Value;
  if (unitsOnOrder == null)
    product.SetUnitsOnOrderNull();
  else
    product.UnitsOnOrder = unitsOnOrder.Value;
  if (reorderLevel == null)
    product.SetReorderLevelNull();
  else
    product.ReorderLevel = reorderLevel.Value;
  product.Discontinued = discontinued;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateProduct(
  // new parameter values
  string productName, int? supplierID, int? categoryID, string quantityPerUnit,
  decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
  short? reorderLevel, bool discontinued, int productID,
  // original parameter values
  string original_productName, int? original_supplierID, int? original_categoryID,
  string original_quantityPerUnit, decimal? original_unitPrice,
  short? original_unitsInStock, short? original_unitsOnOrder,
  short? original_reorderLevel, bool original_discontinued,
  int original_productID)
{
  // 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;
  NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product = products[0];
  // STEP 2: Assign the original values to the product instance
  AssignAllProductValues(product, original_productName, original_supplierID,
    original_categoryID, original_quantityPerUnit, original_unitPrice,
    original_unitsInStock, original_unitsOnOrder, original_reorderLevel,
    original_discontinued);
  // STEP 3: Accept the changes
  product.AcceptChanges();
  // STEP 4: Assign the new values to the product instance
  AssignAllProductValues(product, productName, supplierID, categoryID,
    quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel,
    discontinued);
  // STEP 5: Update the product record
  int rowsAffected = Adapter.Update(product);
  // Return true if precisely one row was updated, otherwise false
  return rowsAffected == 1;
}

第四步: 從ASP.NET頁面把原始值和新值傳入BLL 方法

  完成了DAL和BLL后,剩下的工作就是創建一個能利用系統中內建的開放式并發邏輯的ASP.NET頁面。特別地,數據 Web 服務器控件(GridView,DetailsView或FormView)必須記住它的原始值,并且ObjectDataSource必須同時傳送這兩套值到業務邏輯層。此外,ASP.NET頁面必須加以配置從而適當地處理并發沖突。

  首先,打開EditInsertDelete文件夾中的OptimisticConcurrency.aspx頁面,添加一個GridView控件到設計器,設置它的ID屬性為ProductsGrid。從GridView的職能標記里,選擇創建一個新的ObjectDataSource名為ProductsOptimisticConcurrencyDataSource。既然我們希望這個ObjectDataSource使用支持開放式并發的DAL,就把它配置為使用ProductsOptimisticConcurrencyBLL對象。

圖 13: 該ObjectDataSource使用ProductsOptimisticConcurrencyBLL對象

  在向導中從下拉列表選擇GetProducts,UpdateProduct,和DeleteProduct方法。對UpdateProduct方法,則使用接受所有產品數據字段的重載。

  配置ObjectDataSource控件的屬性

  完成了向導之后,該ObjectDataSource的聲明標記應該如下:

asp:ObjectDataSource ID="ProductsOptimisticConcurrencyDataSource" runat="server"
  DeleteMethod="DeleteProduct" OldValuesParameterFormatString="original_{0}"
  SelectMethod="GetProducts" TypeName="ProductsOptimisticConcurrencyBLL"
  UpdateMethod="UpdateProduct">
  DeleteParameters>
    asp:Parameter Name="original_productID" Type="Int32" />
    asp:Parameter Name="original_productName" Type="String" />
    asp:Parameter Name="original_supplierID" Type="Int32" />
    asp:Parameter Name="original_categoryID" Type="Int32" />
    asp:Parameter Name="original_quantityPerUnit" Type="String" />
    asp:Parameter Name="original_unitPrice" Type="Decimal" />
    asp:Parameter Name="original_unitsInStock" Type="Int16" />
    asp:Parameter Name="original_unitsOnOrder" Type="Int16" />
    asp:Parameter Name="original_reorderLevel" Type="Int16" />
    asp:Parameter Name="original_discontinued" Type="Boolean" />
  /DeleteParameters>
  UpdateParameters>
    asp:Parameter Name="productName" Type="String" />
    asp:Parameter Name="supplierID" Type="Int32" />
    asp:Parameter Name="categoryID" Type="Int32" />
    asp:Parameter Name="quantityPerUnit" Type="String" />
    asp:Parameter Name="unitPrice" Type="Decimal" />
    asp:Parameter Name="unitsInStock" Type="Int16" />
    asp:Parameter Name="unitsOnOrder" Type="Int16" />
    asp:Parameter Name="reorderLevel" Type="Int16" />
    asp:Parameter Name="discontinued" Type="Boolean" />
    asp:Parameter Name="productID" Type="Int32" />
    asp:Parameter Name="original_productName" Type="String" />
    asp:Parameter Name="original_supplierID" Type="Int32" />
    asp:Parameter Name="original_categoryID" Type="Int32" />
    asp:Parameter Name="original_quantityPerUnit" Type="String" />
    asp:Parameter Name="original_unitPrice" Type="Decimal" />
    asp:Parameter Name="original_unitsInStock" Type="Int16" />
    asp:Parameter Name="original_unitsOnOrder" Type="Int16" />
    asp:Parameter Name="original_reorderLevel" Type="Int16" />
    asp:Parameter Name="original_discontinued" Type="Boolean" />
    asp:Parameter Name="original_productID" Type="Int32" />
  /UpdateParameters>
/asp:ObjectDataSource>

  正如你所看到的,DeleteParameters集合包含了對應ProductsOptimisticConcurrencyBLL類的DeleteProduct方法的每一個輸入參數的Parameter實例。同樣地,UpdateParameters集合也包含了對應UpdateProduct每一個輸入參數的Parameter實例。在先前的那些關于數據修改的教程中,我們在這里都會移除ObjectDataSource的OldValuesParameterFormatString屬性,因為這個屬性需要BLL方法既要求傳入原始值也要求傳入修改后的值。此外,這個屬性還需要對應原始值的輸入參數的名稱。既然我們現在要把原始值傳送到BLL,那就不要 刪除這個屬性。

  注意:OldValuesParameterFormatString屬性的值必須映射到BLL里接收原始值的輸入參數的名稱。因為我們把這些參數命名為original_productName,original_supplierID, 等等,我們可以讓OldValuesParameterFormatString屬性的值依舊是original_{0}。然而如果BLL方法的輸入參數名為的old_productName,old_supplierID等等,那么,你不得不把OldValuesParameterFormatString屬性的值改為old_{0}。為了ObjectDataSource能夠正確地將原始值傳送到BLL方法,還有最后一個屬性需要設置。ObjectDataSource有一個 ConflictDetection屬性,它可以設定為下面的 下面兩個值之一:  

 OverwriteChanges – 默認值; 不將原始值發送到BLL方法相應的輸入參數       

 CompareAllValues – 將原始值發送到BLL方法;當使用開放式并發時使用這一項

  稍花些時間將ConflictDetection屬性設置為CompareAllValues。配置GridView的屬性和字段當正確的配置完ObjectDataSource的屬性后,讓我們把注意力放在GridView的設置上。首先,因為我們希望GridView支持編輯和刪除,因此,從GridView的智能標記中點擊添加新列,從下拉列表中選擇CommandField并勾選上“刪除”和“編輯/更新”。這將增加一個CommandField,它的ShowEditButton和ShowDeleteButton屬性都已設置為true。當綁定ProductsOptimisticConcurrencyDataSource ObjectDataSource,該GridView對應每一個產品數據字段都包含一列。

  雖然這樣的一個GridView可以被編輯,但用戶的體驗將是不可接受的。這沒有對數字欄作格式化處理,也沒有validation控件以確保提供product's name并且unit price、units in stock、units on order、和reorder level的值都是大于零的數字。

  跟我們在之前的給編輯和新增界面增加驗證控件 這一節里所論述的一樣,用戶界面可以通過將綁定列(BoundFields)替換為模板列(TemplateFields)實現自定義。我已經通過以下方式修改了這個GridView和它的編輯界面:

1.刪除ProductID、SupplierName、和CategoryName這幾個綁定列;
2.將ProductName綁定列替換為模板列并添加一個RequiredFieldValidation控件;
3.將CategoryID和SupplierID綁定列替換為模板列,并調整編輯界面,使用DropDownList而不是TextBox。在這些模板列的ItemTemplates里,顯示CategoryName和SupplierName字段;
4.將UnitPrice、UnitsInStock、UnitsOnOrder、和ReorderLevel綁定列替換為模板列并添加CompareValidator控件。因為我們在之前的章節里已經詳細說明了如何完成這些任務,我僅僅把最終的聲明語法列出并把具體執行留給讀者作為練習。

asp:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False"
  DataKeyNames="ProductID" DataSourceID="ProductsOptimisticConcurrencyDataSource"
  OnRowUpdated="ProductsGrid_RowUpdated">
  Columns>
    asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
    asp:TemplateField HeaderText="Product" SortExpression="ProductName">
      EditItemTemplate>
        asp:TextBox ID="EditProductName" runat="server"
          Text='%# Bind("ProductName") %>'>/asp:TextBox>
        asp:RequiredFieldValidator ID="RequiredFieldValidator1"
          ControlToValidate="EditProductName"
          ErrorMessage="You must enter a product name."
          runat="server">*/asp:RequiredFieldValidator>
      /EditItemTemplate>
      ItemTemplate>
        asp:Label ID="Label1" runat="server"
          Text='%# Bind("ProductName") %>'>/asp:Label>
      /ItemTemplate>
    /asp:TemplateField>
    asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
      EditItemTemplate>
        asp:DropDownList ID="EditCategoryID" runat="server"
          DataSourceID="CategoriesDataSource" AppendDataBoundItems="true"
          DataTextField="CategoryName" DataValueField="CategoryID"
          SelectedValue='%# Bind("CategoryID") %>'>
          asp:ListItem Value=">(None)/asp:ListItem>
        /asp:DropDownList>asp:ObjectDataSource ID="CategoriesDataSource"
          runat="server" OldValuesParameterFormatString="original_{0}"
          SelectMethod="GetCategories" TypeName="CategoriesBLL">
        /asp:ObjectDataSource>
      /EditItemTemplate>
      ItemTemplate>
        asp:Label ID="Label2" runat="server"
          Text='%# Bind("CategoryName") %>'>/asp:Label>
      /ItemTemplate>
    /asp:TemplateField>
    asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
      EditItemTemplate>
        asp:DropDownList ID="EditSuppliersID" runat="server"
          DataSourceID="SuppliersDataSource" AppendDataBoundItems="true"
          DataTextField="CompanyName" DataValueField="SupplierID"
          SelectedValue='%# Bind("SupplierID") %>'>
          asp:ListItem Value=">(None)/asp:ListItem>
        /asp:DropDownList>asp:ObjectDataSource ID="SuppliersDataSource"
          runat="server" OldValuesParameterFormatString="original_{0}"
          SelectMethod="GetSuppliers" TypeName="SuppliersBLL">
        /asp:ObjectDataSource>
      /EditItemTemplate>
      ItemTemplate>
        asp:Label ID="Label3" runat="server"
          Text='%# Bind("SupplierName") %>'>/asp:Label>
      /ItemTemplate>
    /asp:TemplateField>
    asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit"
      SortExpression="QuantityPerUnit" />
    asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
      EditItemTemplate>
        asp:TextBox ID="EditUnitPrice" runat="server"
          Text='%# Bind("UnitPrice", "{0:N2}") %>' Columns="8" />
        asp:CompareValidator ID="CompareValidator1" runat="server"
          ControlToValidate="EditUnitPrice"
          ErrorMessage="Unit price must be a valid currency value without the
          currency symbol and must have a value greater than or equal to zero."
          Operator="GreaterThanEqual" Type="Currency"
          ValueToCompare="0">*/asp:CompareValidator>
      /EditItemTemplate>
      ItemTemplate>
        asp:Label ID="Label4" runat="server"
          Text='%# Bind("UnitPrice", "{0:C}") %>'>/asp:Label>
      /ItemTemplate>
    /asp:TemplateField>
    asp:TemplateField HeaderText="Units In Stock" SortExpression="UnitsInStock">
      EditItemTemplate>
        asp:TextBox ID="EditUnitsInStock" runat="server"
          Text='%# Bind("UnitsInStock") %>' Columns="6">/asp:TextBox>
        asp:CompareValidator ID="CompareValidator2" runat="server"
          ControlToValidate="EditUnitsInStock"
          ErrorMessage="Units in stock must be a valid number
            greater than or equal to zero."
          Operator="GreaterThanEqual" Type="Integer"
          ValueToCompare="0">*/asp:CompareValidator>
      /EditItemTemplate>
      ItemTemplate>
        asp:Label ID="Label5" runat="server"
          Text='%# Bind("UnitsInStock", "{0:N0}") %>'>/asp:Label>
      /ItemTemplate>
    /asp:TemplateField>
    asp:TemplateField HeaderText="Units On Order" SortExpression="UnitsOnOrder">
      EditItemTemplate>
        asp:TextBox ID="EditUnitsOnOrder" runat="server"
          Text='%# Bind("UnitsOnOrder") %>' Columns="6">/asp:TextBox>
        asp:CompareValidator ID="CompareValidator3" runat="server"
          ControlToValidate="EditUnitsOnOrder"
          ErrorMessage="Units on order must be a valid numeric value
            greater than or equal to zero."
          Operator="GreaterThanEqual" Type="Integer"
          ValueToCompare="0">*/asp:CompareValidator>
      /EditItemTemplate>
      ItemTemplate>
        asp:Label ID="Label6" runat="server"
          Text='%# Bind("UnitsOnOrder", "{0:N0}") %>'>/asp:Label>
      /ItemTemplate>
    /asp:TemplateField>
    asp:TemplateField HeaderText="Reorder Level" SortExpression="ReorderLevel">
      EditItemTemplate>
        asp:TextBox ID="EditReorderLevel" runat="server"
          Text='%# Bind("ReorderLevel") %>' Columns="6">/asp:TextBox>
        asp:CompareValidator ID="CompareValidator4" runat="server"
          ControlToValidate="EditReorderLevel"
          ErrorMessage="Reorder level must be a valid numeric value
            greater than or equal to zero."
          Operator="GreaterThanEqual" Type="Integer"
          ValueToCompare="0">*/asp:CompareValidator>
      /EditItemTemplate>
      ItemTemplate>
        asp:Label ID="Label7" runat="server"
          Text='%# Bind("ReorderLevel", "{0:N0}") %>'>/asp:Label>
      /ItemTemplate>
    /asp:TemplateField>
    asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
      SortExpression="Discontinued" />
  /Columns>
/asp:GridView>

  我們已經非常接近于完成一個完整的例子。然而,還有一些細節問題需要我們慢慢解決。另外,我們還需要一些界面,當發生并發沖突時用來提示用戶。

  注意: 為了讓數據Web服務器控件能夠正確地把原始的值傳送到ObjectDataSource(它隨之將其發送到BLL),將GirdView的EnableViewState屬性設置為true(默認值)是至關重要的。如果禁用了視圖狀態,這些原始值在postback的時候將會丟失

  傳送正確的原始值到ObjectDataSource完成了GridView的配置,還有幾個問題。如果這個ObjectDataSource的ConflictDetection 屬性設置為CompareAllValues (正如我們所做的),它會嘗試復制GridView的原始值到它的Parameter實例。回到圖2查看這個過程的圖解。

  特別需要指出的是,這個GridView的原始值是被指定為雙向綁定的。因此,這些必需的原始值是通過雙向綁定獲取的,并且它們是規定為可改變的格式,這一點很重要。為了看看為什么這一點非常重要,花些時間通過瀏覽器訪問我們的頁面。正如所預料那樣,GridView列出每一個產品,并且每行最左邊的一列都顯示編輯和刪除按鈕。

圖14: GridView列出所有的產品信息

  如果你點擊任意一行的刪除按鈕,則拋出一個FormatException異常。

圖15: 嘗試刪除任意一個產品導致FormatException異常

  當ObjectDataSource試圖讀取原始的UnitPrice值引發了一個FormatException異常。因為該模板列將UnitPrice的值限制為貨幣格式(%# Bind("UnitPrice", "{0:C}") %>),它包含一個貨幣符號,例如$19.95。該FormatException異常發生在ObjectDataSource試圖將字符產轉換成小數。為了繞過此問題,我們有許多種選擇:

1.從模板列里刪除貨幣格式限制。就是說,取代%# Bind("UnitPrice", "{0:C}") %>,簡單地使用%# Bind("UnitPrice") %>。下方的價格就是沒有格式化的。
2.在模板列中顯示UnitPrice時格式化為貨幣,但是使用Eval關鍵字實現綁定。記得Eval是實現單向綁定的。我們仍然需要提供UnitPrice的值作為原始的值,因此在模板列里我們依舊需要一個雙向綁定的聲明,但這可以放在一個Visible屬性設置為false的Label服務器控件里。在模板列里我們可以使用下面的標記:

ItemTemplate>
  asp:Label ID="DummyUnitPrice" runat="server"
    Text='%# Bind("UnitPrice") %>' Visible="false">/asp:Label>
  asp:Label ID="Label4" runat="server"
    Text='%# Eval("UnitPrice", "{0:C}") %>'>/asp:Label>
/ItemTemplate>

3.從模板列里刪除貨幣格式限制,使用 %# Bind("UnitPrice") %>。在GridView的RowDataBound事件處理里,編碼訪問顯示UnitPrice的值的Label服務器控件并設置其Text屬性為格式化的版本。
4.讓UnitPrice保留貨幣格式化。在GridView的RowDeleting事件處理里,將現存的UnitPrice的原始($19.95)替換為實際的小數值(使用Decimal.Parse)。在前面的 在ASP.NET頁面中處理BLL/DAL異常這一節教程里我們也已經看過如何RowUpdating事件處理里實現類似的功能。 在我的例程里我選擇第二種方法,添加一個隱藏的Label服務器控件,并將它的Text屬性雙向綁定到無格式的UnitPrice值。解決了這個問題之后,再次點擊任意一個產品的刪除按鈕。這一次,當ObjectDataSource嘗試調用BLL的UpdateProduct方法時我們得到一個InvalidOperationException異常。

圖 16: ObjectDataSource找不到具有它要發送的輸入參數的方法

  仔細看看異常信息,明顯地ObjectDataSource希望調用一個BLL的DeleteProduct方法,此方法包含original_CategoryName和original_SupplierName輸入參數。這是因為CategoryID和SupplierID模板列的ItemTemplate當前是雙向綁定到CategoryName和SupplierName數據字段。作為替換,我們需要包含對CategoryID和SupplierID數據字段的Bind聲明。為了實現這一點,把現有的Bind聲明更改為Eval聲明,并且添加隱藏的Label服務器控件,這些Label的Text屬性使用雙向綁定的方式綁定到CategoryID和SupplierID數據字段,如下所示:

asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
  EditItemTemplate>
    ...
  /EditItemTemplate>
  ItemTemplate>
    asp:Label ID="DummyCategoryID" runat="server"
      Text='%# Bind("CategoryID") %>' Visible="False">/asp:Label>
    asp:Label ID="Label2" runat="server"
      Text='%# Eval("CategoryName") %>'>/asp:Label>
  /ItemTemplate>
/asp:TemplateField>
asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
  EditItemTemplate>
    ...
  /EditItemTemplate>
  ItemTemplate>
    asp:Label ID="DummySupplierID" runat="server"
      Text='%# Bind("SupplierID") %>' Visible="False">/asp:Label>
    asp:Label ID="Label3" runat="server"
      Text='%# Eval("SupplierName") %>'>/asp:Label>
  /ItemTemplate>
/asp:TemplateField>

  通過這些更改,現在我們可以成功地刪除和編輯產品信息了!在第五步里,我們將看看如何驗證刪除時發生并發沖突。但是現在,花幾分鐘嘗試更新和刪除一些記錄,確認在單用戶的情況下更新和刪除能夠正常運作。

第五步: 測試開放式并發支持

  為了驗證并發沖突是否能夠被發現(而不是導致數據被盲目改寫),我們需要打開兩個瀏覽器窗口來訪問這個頁面。在兩個瀏覽窗口里,都點擊產品“Chai”的編輯按鈕。然后,在其中一個窗口修改其名稱為“Chai Tea”并點擊更新。這個更新應該會成功并且GridView回到預編輯狀態,并且該產品的名稱已經改為“Chai Tea”。
而在另一個瀏覽器窗口里,產品名稱域依舊顯示的是“Chai”。在這個瀏覽器窗口,將UnitPrice的值更新為25.00。如果沒有開放式并發支持的話,點擊第二個瀏覽器窗口的更新按鈕將把產品名稱改回“Chai”,從而覆蓋了第一個瀏覽器窗口里所作的修改。然而現在有了開發式并發,當點擊第二個窗口中的更新按鈕時導致了一個DBConcurrencyException異常。

圖 17: 發現并發沖突,拋出一個DBConcurrencyException異常

  這個DBConcurrencyException異常僅當利用DAL的批量更新模式時會被拋出。直接發送到數據庫的模式則不會引發異常,它僅僅會提示沒有行受到影響。為了舉例說明這個,兩個瀏覽器窗口里的GridView都回到預編輯的狀態。然后,在第一個窗口里,點擊編輯按鈕,把產品名稱從“Chai”改為“Chai Tea”并點擊更新。在第二個窗口里,點擊產品“Chai”的刪除按鈕。點擊刪除按鈕,頁面會傳,GridView調用ObjectDataSource的Delete()方法,然后ObjectDataSource調用ProductsOptimisticConcurrencyBLL類的DeleteProduct方法,傳入原始的值。在第二個瀏覽器窗口里原始的ProductName值是“Chai Tea”,這個值與當前數據庫中相應的ProductName值是不一致的。因此,發送到數據庫的DELETE語句影響0行,因為數據庫中沒有記錄能夠滿足WHERE子句。DeleteProduct方法返回false并且ObjectDataSource的數據重新綁定到GridView控件。

  從最后一個用戶的觀點來看,在第二個瀏覽器窗口里點擊了產品“Chai Tea”的刪除按鈕導致屏幕閃爍,恢復后該產品依舊在,雖然現在它的名稱是“Chai”(在第一個瀏覽器窗口里修改了產品名稱)。如果用戶再次點擊刪除按鈕,這次就能成功刪除,因為GridView的原始的ProductName值(“Chai”)現在能夠與數據庫中相應的值匹配。在這些例子里,用戶的體驗跟理想的狀況還有頗遠的距離。顯然我們在使用批量更新模式時不希望用戶看到DBConcurrencyException異常生硬的詳細信息。并且使用直接發送到數據庫模式的行為也會讓用戶有些疑惑,因為用戶操作失敗了但是沒有準確的提示說明為什么。

  為了補救這兩個小問題,我們可以在頁面上放置一個Label服務器控件,它用來提供為什么更新或刪除失敗的說明。在批量更新模式,我們可以在GridView的post級事件處理里判定是否引發了一個DBConcurrencyException異常,顯示必要的警告標簽。對于直接發送到數據庫的方法,我們可以檢測BLL方法(它對一行或多行產生影響返回true,否則false)的返回值并顯示必要的提示信息。

第六步: 添加提示信息并且在發生并發沖突時顯示

  當一個并發沖突出現時,展現出來的行為取決于是使用DAL的批量更新還是直接發送到數據庫的模式。我們這一節的教程兩種模式都用了,用批量更新模式實現修改,用直接發送到數據庫的方式實現刪除。首先,我們添加兩個Label服務器控件到頁面,它們用來解釋更新或刪除數據時出現的并發沖突。設置Label控件的Visible和EnableViewState屬性為false;這意味一般情況下它們都是隱藏的,除非是那些特別的頁面訪問,在那里它們的Visible屬性通過編碼設置為true。

asp:Label ID="DeleteConflictMessage" runat="server" Visible="False"
  EnableViewState="False" CssClass="Warning"
  Text="The record you attempted to delete has been modified by another user
      since you last visited this page. Your delete was cancelled to allow
      you to review the other user's changes and determine if you want to
      continue deleting this record." />
asp:Label ID="UpdateConflictMessage" runat="server" Visible="False"
  EnableViewState="False" CssClass="Warning"
  Text="The record you attempted to update has been modified by another user
      since you started the update process. Your changes have been replaced
      with the current values. Please review the existing values and make
      any needed changes." />

  在設置了它們的Visible、EnabledViewState和Text屬性之外,我們還要把CssClass屬性設置為Warning,這讓標簽顯示大的、紅色的、斜體、加粗的字體。這個CSS Warning 分類是在研究插入、更新和刪除的關聯事件這一節里添加到Styles.css并且定義好的。添加了這些標簽之后,Visual Studio設計器里看起來應該類似于圖18:

圖 18: 兩個Label控件添加到頁面

  這些Label服務器控件放置到適當的位置后,我們準備好檢測當并發沖突發生時如何判定,在哪個時間點把適當的Label的Visible屬性設置為true并顯示提示信息。

  更新時處理并發沖突

  讓我們首先看看當使用批量更新模式是如何處理并發沖突。因為批量更新模式下的這些沖突導致拋出一個DBConcurrencyException異常,我們需要在ASP.NET頁面中添加代碼來判定更新過程中出現的是否DBConcurrencyException異常。如果是,我們則顯示一個信息向用戶解釋他們的更改沒有被保存,由于別的用戶在他開始編輯和點擊更新按鈕之間的時間里修改了同樣的數據記錄。

  正如我們在在ASP.NET頁面中處理BLL/DAL異常 這一節里看過的那樣,這樣的異常可以在數據Web服務器控件的post級事件處理里被發現和排除。因此,我們需要創建一個GridView的RowUpdated事件的處理,它用來檢測是否拋出了一個DBConcurrencyException異常。這個事件處理通過一個不同的分支區別更新過程中引發的其它異常,如下面的時間處理代碼所示:

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

  面對一個DBConcurrencyException異常,該事件處理顯示UpdateConflictMessage Label控件并且指出該異常已經被處理。正確地編寫了這些代碼后,當更新記錄時發生了并發沖突,用戶的更改會丟失,因為他們不能覆蓋同時發生的另一個用戶的更改。特別地,GridView回到預編輯幢白并且綁定到當前數據庫中數據。這將在GridView的行中顯示出別的用戶的更改,而之前這些更改是看不見的。另外,UpdateConflictMessage Label控件將向用戶說明發生了什么。圖19詳細展示了這一連串的事件。

圖 19: 面對并發沖突,一個用戶的更改丟失了

  注意:作為另一種選擇,與其讓GridView回到預編輯狀態,我們還不如讓GridView停留在編輯狀態,通過設置傳入的GridViewUpdatedEventArgs對象的KeepInEditMode屬性為true。如果你接受這種方法,那么,必須重新綁定數據到GridView(通過調用它的DataBind()方法)從而將其他用戶更改后的值栽入到編輯界面。在這一節的可下載的代碼里,RowUpdated事件處理里有這兩行注悉掉的代碼;僅僅需要啟用這兩行代碼就可以讓GridView在發生了并發沖突之后保留編輯模式。

  響應刪除時的并發沖突

  對于直接發送到數據庫的模式,面對并發沖突時并不會引發異常。然而,數據庫語句不影響任何記錄,因為WHERE子句不能匹配任何記錄。所有在BLL里創建的修改數據的方法都被設計為返回一個布爾值指示它們是否正好影響了一條記錄。因此,為了確定刪除記錄時是否發生了并發沖突,我們可以檢查BLL的DeleteProduct方法的返回值。

  BLL方法的返回值可以在ObjectDataSource的post級事件處理中通過傳入事件處理的ObjectDataSourceStatusEventArgs對象的ReturnValue屬性被檢測。因為我們感興趣的是判斷從DeleteProduct方法返回的結果,我們需要創建一個ObjectDataSource的Deleted事件的事件處理程序。該ReturnValue屬性是object類型的,并且如果在方法可以返回一個值之前引發了異常并且方法被中斷的情況下,它的值也可能為null。所以,我們應該首先確保ReturnValue屬性非空并是個布爾值。若能通過這個檢查,如果ReturnValue是 false我們顯示DeleteConflictMessage Label控件。可以通過下面的代碼完成:

protected void ProductsOptimisticConcurrencyDataSource_Deleted(
  object sender, ObjectDataSourceStatusEventArgs e)
{
  if (e.ReturnValue != null  e.ReturnValue is bool)
  {
    bool deleteReturnValue = (bool)e.ReturnValue;
    if (deleteReturnValue == false)
    {
      // No row was deleted, display the warning message
      DeleteConflictMessage.Visible = true;
    }
  }
}

  面對一個并發沖突,用戶的刪除請求會被取消。GridView被刷新,顯示在用戶載入頁面跟點擊刪除按鈕之間的時間里發生在該記錄上面的更改。當發生這樣的一個沖突,顯示DeleteConflictMessage Label控件,說明發生了什么(見圖20)。

圖 20: 面對并發沖突,一個用戶的刪除請求被取消了

總結

  并發沖突可能存在于所有允許多用戶同時更新或刪除數據的應用程序里。如果不解決這樣的沖突,當兩個用戶同時更新同一條數據,無論誰最后得到“勝利”,都將覆蓋掉另一個用戶所做的更改。作為另一種選擇,開發者可以實現開放式并發控制(optimistic concurrency control),或者保守式并發控制(pessimistic concurrency control)。開放式并發控制假定并發沖突很少發生,簡單地否決一個會提起并發沖突的更新或者刪除命名。保守式并發控制則假定并發沖突頻繁地發生,簡單地拒絕某個用戶的更新或者刪除命令是不可接受的。在保守式并發控制下,編輯一條記錄涉及到鎖定它,從而該記錄被鎖定時預防其他用戶的修改或刪除。

  .NET中的類型化數據集提供了支持開放式并發控制的功能。特別地,發送到數據庫的UPDATE和DELETE語句包含了這個表的所有字段,從而確保了僅當該記錄但前的值與用戶開始他們的修改或更新時的原始值相匹配時,修改或刪除才會發生。一旦DAL配置為支持開放式并發,BLL的方法就需要修改。另外,調用BLL的ASP.NET頁面也需要配置為ObjectDataSource能從它的數據Web服務器控件獲取到這些原始的值并將這些值傳送到BLL。

  正如我們在本節里所看到的,在ASP.NET web應用程序中實現開放式并發控制包括修改DAL和BLL,還包括在ASP.NET頁面中添加相應的支持。無論這些額外的工作對你的時間來說是否一項明智的投入,對你的應用程序來說是否有所成效。如果你極少面對多個用戶同時更新數據,或者不同的用戶對數據作出不同的更改,那么并發控制并非必選項。然而,如果你時常面對多個用戶在線并且對同一些數據進行操作,并發控制可以幫助預防一個用戶的更新或刪除被另一個用戶在不知情的情況下覆蓋。

祝編程快樂!

作者簡介

Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創始人,自1998年以來一直應用微軟Web技術。Scott是個獨立的技 術咨詢顧問,培訓師,作家,最近完成了將由Sams出版社出版的新作,24小時內精通ASP.NET 2.0。他的聯系電郵為mitchell@4guysfromrolla.com,也可以通過他的博客http://ScottOnWriting.NET與他聯系。

您可能感興趣的文章:
  • 讓Win2008+IIS7+ASP.NET支持10萬并發請求
  • c#實現服務器性能監控并發送郵件保存日志
  • C#線程執行超時處理與并發線程數控制實例
  • c#編寫的高并發數據庫控制訪問代碼
  • C#使用隊列(Queue)解決簡單的并發問題
  • 在ASP.NET 2.0中操作數據之四十四:DataList和Repeater數據排序(三)
  • 在ASP.NET 2.0中操作數據之四十五:DataList和Repeater里的自定義Button
  • 在ASP.NET 2.0中操作數據之四十六:使用SqlDataSource控件檢索數據
  • 在ASP.NET 2.0中操作數據之四十七:用SqlDataSource控件插入、更新、刪除數據
  • 在ASP.NET 2.0中操作數據之四十八:對SqlDataSource控件使用開放式并發

標簽:平涼 臨夏 清遠 慶陽 海西 中衛 聊城 甘肅

巨人網絡通訊聲明:本文標題《在ASP.NET 2.0中操作數據之二十一:實現開放式并發》,本文關鍵詞  在,ASP.NET,2.0,中,操作,數據,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《在ASP.NET 2.0中操作數據之二十一:實現開放式并發》相關的同類信息!
  • 本頁收集關于在ASP.NET 2.0中操作數據之二十一:實現開放式并發的相關信息資訊供網民參考!
  • 推薦文章
    主站蜘蛛池模板: 郑州龙阳重型机械设备有限公司| 南京华创包装机械设备有限公司 | 石化机械制造有限公司| 巩义市瑞赛克机械设备有限公司| 浙江海天机械有限公司| 杭州旭众机械设备有限公司| 辽宁 机械制造有限公司| 浙江勇力机械有限公司| 新乡市长城机械制造有限公司| 郑州江河重工有限公司| 佛山市优霸机械设备有限公司 | 大连龙尧塑料机械有限公司 | 郑州祥龙建筑机械租赁有限公司| 普瑞特机械有限公司| 云南中天机械有限公司| 北京大铭世进机械设备有限公司| 南京利德盛机械有限公司| 济南锐捷机械设备有限公司| 湖南中旺工程机械设备有限公司| 广东星联精密机械有限公司| 保东农业机械有限公司| 无锡鹰普机械有限公司| 浙江速成精密机械有限公司| 机械设计 有限公司| 浙江康明斯机械有限公司| 上海塑帝机械有限公司| 广东重工建设监理有限公司怎么样 | 昌利机械制造有限公司| 佛山市奥索包装机械有限公司 | 郑州鼎盛机械有限公司| 重庆江增船舶重工有限公司| 杭州同创顶立机械有限公司| 河北神耕机械有限公司| 好烤克食品机械有限公司| 浙江网路崛起有限公司| 泰田液压机械有限公司| 重庆志成机械有限公司| 山西机械制造有限公司| 大方起重机械有限公司| 普惠环保机械有限公司| 河南国起泵业有限公司| 新乡市中天机械有限公司| 天津的机械设备有限公司| 广州东昇机械有限公司| 广州佳速精密机械有限公司| 无锡速波精密机械有限公司| 江苏八达重工机械有限公司| 成都金瑞建工机械有限公司| 沈阳联合利邦机械有限公司| 上海开隆冶金机械制造有限公司 | 兴鑫钢铁有限公司电话| 北京 机械有限公司| 南京南特精密机械有限公司| 上海涟恒精密机械有限公司| 浙江万龙机械有限公司| 山东宝星机械有限公司| 南通宝钢钢铁有限公司| 广州金宗机械有限公司| 天津京龙工程机械有限公司| 黄石华旦机械制造有限公司| 莒县长运机械有限公司| 常州斯太尔动力机械有限公司| 无锡旭英机械有限公司| 山东 钢铁有限公司| 北京起重设备有限公司| 常德纺织机械有限公司| 上海玉兆精密机械有限公司| 无锡市机械制造有限公司| 蓬莱大金海洋重工有限公司| 山东浩信机械有限公司| 东阳市机械有限公司| 山东莱芜煤矿机械有限公司| 恒利达机械有限公司| 芜湖灵芝机械有限公司| 钜业机械设备有限公司| 约翰迪尔佳木斯农业机械有限公司| 无锡市机械制造有限公司| 常州市工程机械有限公司| 安徽富鑫钢铁有限公司| 江苏长虹涂装机械有限公司 | 重庆红江机械有限公司| 华夏机械设备有限公司| 玛连尼 法亚机械有限公司| 南京赛达机械制造有限公司 | 浩博海门机械有限公司| 浙江荣升机械有限公司| 三国精密机械有限公司| 宁波将军机械有限公司| 广东巨风机械制造有限公司 | 宁波东力机械制造有限公司| 山东云光钢铁有限公司| 浙江合诺机械有限公司| 科倍隆南京机械有限公司| 山东造纸机械厂有限公司 | 欧力特机械有限公司| 佛山市机械制造有限公司| 日照市机械有限公司| 洛阳古城机械有限公司| 盐城市机械有限公司| 天津起重机械有限公司| 山东华伟重工机械有限公司| 河南三兄重工有限公司| 常熟机械制造有限公司| 郑州机械设备有限公司| 东阳市佳先机械制造有限公司| 太仓越华精密机械配件有限公司| 辽宁泰威机械制造有限公司| 诸城市志诺机械有限公司| 重工机械制造有限公司| 诺曼艾索机械技术(北京)有限公司| 上海钊凯包装机械有限公司| 四川晶工机械有限公司| 江苏百德机械有限公司| 常州国丰机械有限公司| 成都海科机械设备制造有限公司| 四川建筑机械有限公司| 杭州海陆重工有限公司| 浙江华球机械制造有限公司| 佳友精密机械有限公司| 吉首市中诚制药机械有限公司| 新乡市辰威机械有限公司| 河南昌申钢铁有限公司| 烟台天成机械有限公司| 随州市恒大机械铸造有限公司| 浙江兴发机械有限公司| 单县江华机械有限公司| 上海汉 机械有限公司| 徐州世通重工机械制造有限公司 | 上海拓稳机械有限公司| 永腾弹簧机械设备有限公司| 无锡市江益液压机械成套有限公司| 潍坊凯隆机械有限公司| 温州立胜印刷包装机械有限公司| 哈尔滨联科包装机械有限公司| 新疆汇合钢铁有限公司| 江苏金沃机械有限公司| 济宁 机械有限公司| 天津市天机液压机械有限公司| 四川欧曼机械有限公司| 宁波昌扬机械工业有限公司| 江阴派格机械设备有限公司| 青岛软控重工有限公司| 苏州升降机械有限公司| 台州万洲机械有限公司| 衡水机械制造有限公司| 玉环万全机械有限公司| 佛山市机械设备有限公司| 山东 重工有限公司| 广州萱裕机械有限公司| 上海市机械有限公司| 安徽唐兴机械装备有限公司| 汕头机械设备有限公司| 铁建重工包头有限公司| 山东通佳机械有限公司| 常州新燎原机械有限公司| 深圳市奥德机械有限公司| 湖北铁正机械有限公司| 宁夏瑞光机械有限公司| 鹤壁万丰矿山机械制造有限公司| 首钢凯西钢铁有限公司| 东莞市三米通用机械有限公司 | 浙江雷克机械工业有限公司| 浙江德鹏机械有限公司| 天津 机械 有限公司| 青岛同三塑料机械有限公司| 上海慕鼎机械设备有限公司| 德州宝鼎液压机械有限公司| 五谷酿机械有限公司| 杭州建明机械有限公司| 柳州市超凌顺机械制造有限公司| 恩德特机械(苏州)有限公司| 山东华屹重工有限公司| 成都松茂工程机械有限公司| 浙江起重机械有限公司| 德州宝鼎液压机械有限公司| 北京起重机械有限公司| 宁波翠科机械有限公司| 南京伟舜机械有限公司| 济南大彤机械设备有限公司| 浙江建设机械有限公司| 辽宁春光机械有限公司| 江苏苏东机械有限公司| 杭州龙云水利机械制造有限公司 | 长江机械设备有限公司| 宁江精密机械有限公司| 广州市 工程机械有限公司| 洛阳钢峰机械有限公司| 湖北日朗机械制造有限公司| 河北唐银钢铁有限公司| 南通明诺机械有限公司| 江苏久保田农机机械有限公司| 黑龙江建龙钢铁有限公司| 安徽唐兴机械装备有限公司| 昆山市烽禾升精密机械有限公司| 洛阳工程机械有限公司| 永红铸造机械有限公司| 常州达德机械有限公司| 河北迪森机械制造有限公司| 青岛正机械有限公司| 厦门升正机械有限公司| 天津市钢铁贸易有限公司| 上海相宜机械有限公司| 江阴市中立机械工业有限公司| 中核华兴达丰工程机械有限公司 | 首钢凯西钢铁有限公司| 中阳钢铁有限公司招聘| 凯达机械制造有限公司| 上海宝丰机械制造有限公司 | 有限公司名字起名大全| 江苏普格机械有限公司| 泉州佳升机械有限公司| 佛山星光传动机械有限公司 | 苏州朗威电子机械有限公司| 合肥中通抛光机械有限公司| 吉林鑫达钢铁有限公司地址| 机械(昆山)有限公司| 无锡双益精密机械有限公司| 如皋市通达机械制造有限公司| 浙江纺织机械有限公司| 东莞市旭田包装机械有限公司| 宁波人和机械轴承有限公司| 易百通机械有限公司| 成都万欣邦达机械制造有限公司| 机械密封件有限公司| 浙江万能弹簧机械有限公司| 浙江联科机械有限公司| 上海申越包装机械制造有限公司| 青岛凯机械有限公司| 大连铸鸿机械有限公司| 三马起重机有限公司| 徐州随车起重机有限公司| 郑州双狮粮油机械有限公司| 汉邦机械制造有限公司| 济南鑫聚德机械有限公司| 武汉食品机械有限公司| 常州昊博机械有限公司| 无锡双象橡塑机械有限公司| 山东冠华重工机械有限公司| 星光传动机械有限公司| 杭州千和精密机械有限公司| 张家港长力机械有限公司| 济宁立派工程机械有限公司| 桂林恒达矿山机械有限公司| 盐城机械制造有限公司| 杭州博创机械有限公司| 南通赛孚机械设备有限公司| 江阴凯澄起重机械有限公司| 泉州市闽达机械制造有限公司| 广州广重分离机械有限公司| 重庆舰帏机械有限公司| 山东兖州煤矿机械有限公司| 湘潭丰弘机械制造有限公司 | 常州奥恒机械有限公司| 上海相宜机械有限公司| 葛洲坝机械船舶有限公司| 昆山胜代机械有限公司| 伯曼机械制造有限公司| 东营海河机械有限公司| 唐山瑞兴钢铁有限公司| 莱州华汽机械有限公司| 江苏宏博机械制造有限公司| 杭州嘉诚机械有限公司| 无锡印染机械有限公司| 飞虎机械制造有限公司| 南牧机械有限公司招聘| 大连工进机械制造有限公司| 徐州中嘉工程机械有限公司 | 天津市液压机械有限公司| 常州宝菱重工机械有限公司| 深圳市精密达机械有限公司| 山东鲁机械有限公司| 新乡市豫成振动机械有限公司| 郑州谷丰机械设备有限公司 | 宁波精密机械有限公司| 浙江帅锋精密机械制造有限公司 | 东莞胜通机械有限公司| 上海陵城机械有限公司| 郑州矿山机械有限公司| 中远海运重工有限公司| 临沂工程机械有限公司| 元机械制造有限公司| 大江重工焦作有限公司| 江苏大圣机械制造有限公司| 徐州成日钢铁有限公司| 扬州凯勒机械有限公司| 宁波甬龙机械有限公司| 上海巨远塑料机械有限公司| 浙江海工机械有限公司| 嘉兴机械有限公司招聘| 山西 机械有限公司| 上海铮潼起重机电设备有限公司 | 山东祥远机械有限公司| 上海唐迪机械制造有限公司| 上海爱德夏机械有限公司| 青岛如隆机械有限公司| 溧阳申特钢铁有限公司| 重庆宏塑机械有限公司| 苏州博杰思达机械有限公司| 苏州敏喆机械有限公司| 苏州勤美达精密机械有限公司 | 常熟 机械 有限公司| 杭州萧山凯兴食品机械有限公司| 郑州品创机械设备有限公司| 杭州方圆塑料机械有限公司 | 卓郎纺织机械有限公司| 广东正力精密机械有限公司| 江苏天泽精工机械有限公司| 江阴鼎力起重机械有限公司| 山西机械制造有限公司| 郑州市机械设备有限公司| 石家庄米兹机械设备有限公司| 广州凯诺机械有限公司| 山东卡特重工有限公司| 温州利波机械有限公司| 温州市机械有限公司| 佛山市奥索包装机械有限公司 | 济宁福瑞得机械有限公司| 湖南申德钢铁有限公司| 徐州徐工基础工程机械有限公司| 佛山顺德木工机械有限公司| 四川盛和机械设备有限公司| 滕州三合机械有限公司| 厦门厦工机械有限公司| 中兴机械制造有限公司| 湖州汇大机械有限公司| 常州亚美柯机械设备有限公司| 英国敬业钢铁有限公司| 深圳市德润机械有限公司| 江阴宗承钢铁有限公司| 安丘市 机械有限公司| 山东瑞浩重型机械有限公司| 大连升隆机械有限公司| 莱州 机械有限公司| 三星重工业宁波有限公司招聘| 萨驰华辰机械 苏州 有限公司| 昆山东新力特精密机械有限公司| 瑞安市机械有限公司| 河南万杰食品机械有限公司| 山东明宇重工机械有限公司| 广州新成机械技术有限公司| 鑫达机械制造有限公司| 上海凌鹰机械有限公司| 上海丰禾精密机械有限公司| 常州市佳凯包装机械有限公司| 广州晶冠机械有限公司| 郑州水工机械有限公司招聘| 重庆远风机械有限公司| 北京恒机械有限公司| 洛阳耿力机械有限公司| 重庆足航钢铁有限公司| 禹城市华普机械设备有限公司| 研精舍上海精密机械加工有限公司| 浙江易锋机械有限公司| 北京富佳伟业机械制造有限公司| 哈尔滨纳诺机械设备有限公司| 武汉山推机械有限公司| 朝阳重工机械有限公司| 北京液压机械有限公司| 肥城云宇机械有限公司| 苏州 工业机械有限公司| 华东油压机械制造有限公司| 浙江新德宝机械有限公司| 苏州博杰思达机械有限公司| 中实洛阳重型机械有限公司 | 郑州华龙机械工程有限公司| 泉州泉丰机械有限公司| 堃霖冷冻机械有限公司| 无锡市双瑞机械有限公司| 天津金都钢铁有限公司| 广州凯诺机械有限公司| 金泰机械制造有限公司| 哈克农业机械装备制造有限公司| 众力达机械有限公司| 四川依赛特机械制造有限公司| 厦门洪海机械有限公司| 养殖有限公司起名大全| 浙江矿山机械有限公司| 陕西金奇机械电器制造有限公司| 重庆捷庆机械有限公司| 重庆箭驰机械有限公司| 安阳市赛尔德精工机械有限公司| 广州机械设备有限公司| 天津菲特机械有限公司| 山东莱州机械有限公司| 科倍隆南京机械有限公司| 湖北首开机械有限公司| 上海台新食品机械有限公司| 唐山前进钢铁有限公司| 无锡华迪机械设备有限公司| 邯郸海拓机械有限公司| 山东动力机械有限公司| 锦州俏牌机械有限公司| 东莞市业佳精密机械有限公司 | 青岛华鑫克斯顿机械有限公司 | 青岛浩翔机械有限公司| 斯特机械制造有限公司| 广州铸星机械有限公司| 江苏华夏重工有限公司| 滕州市美力机械有限公司| 江苏甲钢钢铁有限公司| 汕头市伟力塑料机械厂有限公司| 和和机械(张家港)有限公司| 北京欧力源机械有限公司| 江苏液压机械有限公司| 亚龙机械制造有限公司| 南京阿特拉斯机械设备有限公司| 宁波信泰机械有限公司| 苏州奥达机械部件有限公司| 柳州富达机械有限公司官网| 武汉瑞威特机械有限公司| 上海新沪机械有限公司| 郑州市鑫宇机械制造有限公司 | 河南 工程机械有限公司| 昆山之富士机械制造有限公司| 上海化工机械厂有限公司| 无锡新世杰辊压机械有限公司| 郑州液压机械有限公司| 济南庚辰钢铁有限公司| 曲阜兴运输送机械设备有限公司 | 烟台市利达木工机械有限公司| 宝索机械制造有限公司| 上海集嘉机械有限公司| 唐山利军机械有限公司| 陕西锦泰机械有限公司| 山东钢铁贸易有限公司| 合肥永升机械有限公司| 东莞市机械有限公司| 天津京龙工程机械有限公司| 青岛鲁奥机械有限公司| 辽宁三君工程机械有限公司| 重庆宝汇跨搏机械制造有限公司| 宜昌 机械有限公司| 上海建设路桥机械设备有限公司 | 阿特拉斯机械设备有限公司| 长春协展机械工业有限公司| 盐城市机械有限公司| 昆山 环保机械有限公司| 恩比尔(厦门)机械制造有限公司 | 渤海重工管道有限公司| 东莞市兆恒机械有限公司| 新乡市西贝机械有限公司| 温岭林大机械有限公司| 宿迁 机械 有限公司| 鞍山机械重工有限公司| 徐州徐工施维英机械有限公司| 辽阳筑路机械有限公司| 福建巨邦机械有限公司| 京西重工北京有限公司| 东莞市鑫国丰机械有限公司| 南通佳吉机械有限公司| 徐州天地重型机械制造有限公司| 成都宏机械有限公司| 常德纺织机械有限公司| 郑州华隆机械制造有限公司| 山西中宇钢铁有限公司| 青岛如隆机械有限公司| 常州双鸟起重机械有限公司| 山西汉通机械有限公司| 聊城 机械 有限公司| 宣城市 机械 有限公司| 温州润新机械制造有限公司| 山东顺达机械有限公司| 上海沪工起重机械有限公司| 常州好迪机械有限公司| 温州科迪机械有限公司| 威海石岛重工有限公司| 苏州在田机械有限公司| 汕头市包装机械有限公司| 东莞智荣机械有限公司| 江阴市江南轻工机械有限公司| 江苏双友重型机械有限公司| 常州立达纺织机械有限公司| 万兹莱压缩机械(上海)有限公司| 江苏中闽钢铁有限公司| 青岛璞盛机械有限公司| 海瑞克隧道机械有限公司| 浙江恒通机械有限公司| 浙江名瑞机械有限公司| 四川青城机械有限公司| 郑州鼎盛机械设备有限公司| 阳宏机械制造有限公司| 台州 精密机械有限公司| 机械成套设备有限公司| 中实洛阳重型机械有限公司实习报告 | 广东省重工建筑设计院有限公司| 江阴市三 机械有限公司| 无锡化工机械有限公司| 厦门机械设备有限公司| 天津艾尔特精密机械有限公司| 重庆舰帏机械有限公司| 海盐鼎盛机械有限公司| 杭州神钢建设机械有限公司| 青岛越海机械有限公司| 新乡市海纳筛分机械制造有限公司| 机械有限公司 张家港| 江苏华光双顺机械制造有限公司| 昆山苏隆机械制造有限公司| 诸城市美川机械有限公司| 飞迈烟台机械有限公司| 新疆机械设备有限公司| 潍坊中迪机械有限公司| 上海浩勇精密机械有限公司| 廊坊德基机械有限公司| 徐工辽宁机械有限公司| 上海诺 机械有限公司| 上海岭申机械有限公司| 十堰福堰钢铁有限公司| 扬州涂装机械有限公司| 江苏久保田农机机械有限公司| 新乡市起重机厂有限公司| 青岛希世可机械有限公司| 泉州泉盛机械有限公司| 宁波佳利来机械制造有限公司| 玉环锐利机械有限公司| 上海石油机械有限公司| 浙江美华包装机械有限公司| 海宁亚东机械有限公司| 集瑞联合重工有限公司| 河北巨牛机械有限公司| 天阳机械制造有限公司| 常州纺织机械有限公司| 中山弘立机械有限公司| 江苏金韦尔机械有限公司| 无锡博雅德精密机械有限公司| 洛阳天宇机械制造有限公司| 上海景林包装机械有限公司| 德州佳永机械制造有限公司| 江苏腾通包装机械有限公司| 绿友园林机械有限公司| 广州闽欣机械设备有限公司| 江西江锻重工有限公司| 沈阳重型机械有限公司| 山东杰卓机械有限公司| 浙江万龙机械有限公司| 中山 机械 有限公司| 淮安华辉机械设备有限公司| 日照瑞荣机械有限公司| 上海轻工机械有限公司| 上海青川机械配件有限公司| 厦门众达钢铁有限公司| 宁波工程机械有限公司| 上海山美重型矿山机械有限公司| 济南帕特机械有限公司| 石家庄 机械有限公司| 浙江东雄重工有限公司| 宁波思进机械有限公司| 邢台 机械有限公司| 德蒙压缩机械有限公司| 山东曲阜机械有限公司| 河南飞龙工程机械制造有限公司| 大丰奥泰机械有限公司| 陕西 机械设备有限公司| 武汉创联机械有限公司| 苏州奥德机械有限公司| 阜新恒泰机械有限公司| 武汉食品机械有限公司| 中航起落架有限公司| 龙扬机械)有限公司| 广州文穗塑料机械有限公司| 浙江新德宝机械有限公司| 青岛谊金华塑料机械有限公司| 洛阳中收机械装备有限公司| 烟台海兰德机械设备有限公司| 深圳中施机械设备有限公司 | 天津华悦包装机械有限公司| 全精密机械有限公司| 广州美特机械有限公司| 丰精密机械有限公司| 常州锐展机械有限公司| 郑州中嘉重工有限公司| 营口嘉晨钢铁有限公司| 三菱重工海尔空调机有限公司 | 济南鑫金龙机械有限公司| 禹城 机械 有限公司| 江西神起信息技术有限公司| 东莞市印刷机械有限公司| 莱州市华弘机械有限公司| 郑州市机械设备有限公司| 上海树新机械有限公司| 长江液压机械有限公司| 山东鑫弘重工有限公司| 鼎工机械制造有限公司| 上海嘉峥机械有限公司| 上海世邦机械有限公司| 承德 机械有限公司| 厦门宇龙机械有限公司| 洛阳鹏起实业有限公司怎么样| 宁波瑞铭机械有限公司| 抚顺中兴重工有限公司| 济南钢铁贸易有限公司| 豪利机械苏州有限公司| 杭州丰波机械有限公司| 上海汉虹精密机械有限公司| 郑州永兴重工机械有限公司| 合肥华运机械有限公司| 青岛武船重工有限公司| 南通凯迪自动机械有限公司 | 珠海华亚机械有限公司| 海安机械制造有限公司| 广州铸星机械有限公司| 唐山众达机械轧辊有限公司 | 上海连富机械有限公司| 温州 轻工机械有限公司| 苏州金韦尔机械有限公司| 常州万高机械制造有限公司| 焦作市机械有限公司| 安徽永成电子机械技术有限公司| 济南诺斯机械有限公司| 河北常富机械有限公司| 南京聚力化工机械有限公司| 武汉中粮机械有限公司| 济南龙安机械有限公司| 无锡市光彩机械制造有限公司| 湖南卓迪机械有限公司| 青岛恒林机械有限公司| 济南金梭机械制造有限公司| 宁波塑料机械制造有限公司| 常州嘉耘机械有限公司| 德国arku机械制造有限公司| 山东腾机械有限公司| 卓郎智能机械有限公司| 江苏方圣机械有限公司| 上海江浪流体机械制造有限公司| 宁波中机械有限公司| 江苏舜工机械有限公司| 东莞市金拓机械有限公司| 山东联亿重工有限公司| 聊城 机械 有限公司| 鞍山宝得钢铁有限公司| 大连卓远重工有限公司| 江苏佳粮机械有限公司| 青岛鲁奥机械有限公司| 武汉山推机械有限公司| 莱州 机械有限公司| 固精密机械有限公司| 利星行机械有限公司| 江苏百德机械有限公司| 山东鲁樽机械有限公司| 江苏贸隆机械制造有限公司| 泸州长江工程机械成套有限公司 | 廊坊德基机械有限公司| 济南 机械设备有限公司| 南通奥普机械工程有限公司| 杭州鼎升机械有限公司| 昆山河海精密机械有限公司| 苏州精雕精密机械工程有限公司| 台在机械设备有限公司| 上海电工机械有限公司| 浙江万能弹簧机械有限公司| 昆山精密机械有限公司| 阳春新钢铁有限公司| 四川宏华友信石油机械有限公司| 余姚 机械 有限公司| 大连工程机械有限公司| 江苏骏马压路机械有限公司| 潍坊二川机械有限公司| 上海钢铁交易中心有限公司| 南京登峰起重设备制造有限公司 | 杭州大禹机械有限公司| 北京洛克机械有限公司| 昆山圣源机械有限公司| 河南兴邦重工机器有限公司| 山东精诺机械有限公司| 广东龙辉基业建筑机械有限公司| 东元精密机械有限公司| 浙江美华包装机械有限公司| 吉林鑫达钢铁有限公司地址| 河南通达重工有限公司| 北京大铭世进机械设备有限公司 | 青岛日佳机械有限公司| 温州万润机械有限公司| 深圳市精密机械有限公司| 安徽玻璃机械有限公司| 常州耐强传动机械有限公司| 山东动力机械有限公司| 河北食品机械有限公司| 沈阳重工机械有限公司| 宁波特艾科机械制造有限公司 | 盐城机械设备有限公司| 顺德机械设备有限公司| 昆明机械制造有限公司| 广州 机械 有限公司| 机械有限公司 张家港| 河南天力起重机械有限公司| 吴江迈锐机械有限公司怎么样 | 上海百勤机械有限公司| 华菱涟源钢铁有限公司| 扬州三源机械有限公司| 深圳新添润彩印机械设备有限公司| 浙江迅定钢铁有限公司| 威海泓意机械有限公司| 江苏新益机械有限公司| 徐州福曼随车起重机有限公司| 宁波迈拓斯数控机械有限公司| 浙江志高机械有限公司| 射阳 机械有限公司| 广州众起办公用品有限公司| 昆玉钢铁有限公司招聘| 河南朝阳钢铁有限公司| 浙江胜祥机械有限公司| 禹州市机械有限公司| 南京 机械设备有限公司| 德锐尔机械有限公司| 上海久协机械设备有限公司| 堃霖冷冻机械有限公司| 曲阜机械设备有限公司| 广州中益机械有限公司| 东莞 精密机械有限公司| 浙江歌德起重机有限公司| 天津华悦包装机械有限公司 | 东风井关农业机械有限公司| 连云港市机械有限公司| 东阳市佳先机械制造有限公司| 新乡市佳盛振动机械有限公司| 郑州同鼎机械设备有限公司| 武汉格瑞拓机械有限公司| 上海众和包装机械有限公司 | 无锡精密机械有限公司| 徐州徐工随车起重机有限公司| 江南起重机械有限公司| 上海铮潼起重机电设备有限公司| 浙江万宝机械有限公司| 溧阳三元钢铁有限公司| 常州奥恒机械有限公司| 威塑料机械有限公司| 靖江机械制造有限公司| 唐山佳鑫机械配件有限公司 | 江苏宏威重工机床制造有限公司| 东莞高盟机械有限公司| 郑州博源机械有限公司| 厦门大金机械有限公司| 山西中德科工机械制造有限公司| 河南江河机械有限公司| 江苏青山机械有限公司| 绵阳新晨动力机械有限公司| 上海国翔包装机械制造有限公司 | 上海冠隆阀门机械有限公司| 同向精密机械有限公司| 湖南机械制造有限公司| 沧州卓鑫机械设备制造有限公司| 山东同力达智能机械有限公司| 高密高锻机械有限公司| 杭州杭顺机械有限公司| 南通天成机械有限公司| 赣州群星机械有限公司| 安徽精密机械有限公司| 湖南龙凤机械制造有限公司| 上海尼法机械有限公司| 焦作泰鑫机械有限公司| 高明鸿溢机械有限公司| 苏州柯瑞机械有限公司| 台州亚格机械有限公司| 洛阳福格森机械装备有限公司| 海南建设工程机械施工有限公司| 上海恒麦食品机械有限公司| 东莞沃德精密机械有限公司| 南通新兴机械制造有限公司| 山东华准机械有限公司| 新疆昆仑钢铁有限公司| 上海胜松机械制造有限公司| 临沂三友重工有限公司| 青岛工程机械有限公司| 江苏银华春翔机械制造有限公司 | 四川德盛钢铁有限公司| 廊坊中建机械有限公司| 晋江市机械有限公司| 合肥金锡机械有限公司| 厦门黎明机械有限公司| 大连胜龙包装机械有限公司 | 洛阳鹏起实业有限公司| 宁波 钢铁有限公司| 广州善友机械设备有限公司| 上海玉程机械有限公司| 哈尔滨机械设备有限公司| 山东广富钢铁有限公司| 上海沁艾机械设备有限公司| 深圳市力豪机械设备有限公司| 宁波凯特机械有限公司| 佛山丰又丰机械有限公司| 江苏双箭输送机械有限公司| 输送机械制造有限公司| 廊坊德基机械有限公司| 绍兴市 机械有限公司| 重庆德运机械制造有限公司| 西安北村精密机械有限公司| 常州自力化工机械有限公司| 常州柳工机械有限公司| 上海亚华印刷机械有限公司 | 东莞市途锐机械有限公司| 杭州起重机械有限公司| 东莞市力华机械设备有限公司 | 上海青川机械配件有限公司| 昆山之富士机械制造有限公司| 西安冠杰机械设备有限公司| 无锡腾力机械有限公司| 武汉金火旺机械设备有限公司| 杭州金竺机械有限公司| 包头吉宇钢铁有限公司| 南京建克机械有限公司| 上海起重设备有限公司| 柳州丹顺机械有限公司| 无锡大昌机械工业有限公司| 上海取祥机械有限公司| 机械化施工有限公司| 临沂美联重工有限公司| 漳州三宝钢铁有限公司| 三菱重工空调有限公司| 辽宁泰威机械制造有限公司| 上海鹰宏机械有限公司| 浙江瑞大机械有限公司| 吉林鑫达钢铁有限公司| 青岛大牧人机械有限公司| 河北迪森机械制造有限公司| 抚顺机械设备制造有限公司| 华菱涟源钢铁有限公司| 张家港市港丰机械有限公司| 河南省新乡市矿山起重机有限公司| 济南迈动数控机械有限公司| 青岛包装机械有限公司| 广东重工监理有限公司| 新科起重机有限公司| 重庆恒科机械制造有限公司| 山东银鹰炊事机械有限公司| 青岛昌佳机械有限公司| 德州联合石油机械有限公司| 上海机械刀片有限公司| 江苏华澄重工有限公司| 福州恒拓机械有限公司| 辽阳新达钢铁有限公司| 无锡印染机械有限公司| 皋兰兰鑫钢铁有限公司| 华新机械有限公司官网| 新疆昆仑钢铁有限公司| 浙江联科机械有限公司| 潍坊西泰机械有限公司| 上海普顺机械电器制造有限公司| 广东佛山机械有限公司| 山东国丰机械有限公司| 定州宏远机械有限公司| 青岛辉腾机械有限公司| 苏州苏安起重吊装有限公司| 梁山机械制造有限公司| 常州天山重工机械有限公司| 泸州发展机械有限公司| 四平方向机械有限公司| 无锡光良塑料机械有限公司| 盐城市鑫益达精密机械有限公司| 诸城市机械有限公司| 南通安港机械有限公司| 浙江万通重工有限公司| 山东瑞华机械有限公司| 海的动力机械有限公司| 泰州机械 有限公司| 河北永明地质工程机械有限公司| 洛阳鹏起实业有限公司| 衡阳华意机械有限公司| 海门亿峰机械有限公司| 宝鸡忠诚制药机械有限公司| 东莞%机械%有限公司| 天工机械制造有限公司| 浙江宇丰机械有限公司| 工程机械租赁有限公司| 河北液压机械有限公司| 浙江高达机械有限公司| 威海环宇化工机械有限公司| 恒联食品机械有限公司| 玉溪新兴钢铁有限公司| 潍坊润鑫机械有限公司| 浙江华业塑料机械有限公司| 福建瑜鼎机械有限公司| 溧阳金纬机械有限公司| 河北明芳钢铁有限公司| 顺德富华工程机械制造有限公司 | 浙江双畅起重机械有限公司| 泉州群峰机械有限公司| 河南耿力机械有限公司| 山东逸通机械有限公司| 上海昊农农业机械有限公司| 深圳新劲力机械有限公司| 佛山市中牌机械有限公司| 无锡东源机械制造有限公司| 上海凌鹰机械有限公司| 珠海华亚机械有限公司| 温岭联星机械有限公司| 圣博液压机械有限公司| 杭州中力机械设备有限公司| 无锡科创机械设计制造有限公司| 洛阳中收机械装备有限公司招聘 | 苏州爱德克精密机械有限公司| 永兴机械设备有限公司| 河南省新乡市矿山起重机有限公司 | 国机重工洛阳有限公司| 厦门洪海机械有限公司| 贵州红林机械有限公司| 河北兴华钢铁有限公司| 昆山乙盛机械工业有限公司| 诚鑫诚机械有限公司| 海的动力机械有限公司| 合肥市春晖机械制造有限公司| 浙江鑫辉机械有限公司| 杭州中力机械有限公司| 德昌誉机械制造有限公司| 山东明沃机械有限公司| 瑞安市华东包装机械有限公司 | 机械设备有限公司招聘| 张家港市港丰机械有限公司| 济南钢铁 有限公司| 大连船舶重工有限公司| 郑州鼎盛机械有限公司| 沃洲机械制造有限公司| 江苏海陵机械有限公司| 重庆墨龙机械有限公司| 州东方机械有限公司| 大连 机械有限公司| 邢台凌远机械制造有限公司| 苏州力强机械制造有限公司| 湖南德邦重工机械有限公司| 河北华昌机械设备有限公司| 河南路友机械有限公司| 江苏巨风机械制造有限公司| 昌邑市机械有限公司| 上海冠隆阀门机械有限公司| 南通惠生重工有限公司| 文水海威钢铁有限公司| 浙江 动力机械有限公司| 建设工程有限公司起名| 上海霏润机械设备有限公司| 曲阜志成机械有限公司| 合肥旭龙机械有限公司| 江阴市液压机械有限公司| 山东荣利中石油机械有限公司 | 长春 机械 有限公司| 佛山市海裕机械有限公司| 株洲机械制造有限公司| 杭州海特机械有限公司| 上海国翔包装机械制造有限公司| 江苏新美星包装机械有限公司| 大连世达重工有限公司| 永达机械制造有限公司| 广州机械租赁有限公司| 昆山市升达机械制造有限公司 | 东莞市凯奥机械有限公司| 江苏化工机械有限公司| 合肥中达机械制造有限公司| 河南北工机械制造有限公司| 武汉精密机械有限公司| 温州利捷机械有限公司| 东莞市沃德精密机械有限公司| 建筑工程有限公司起名| 广州振通机械有限公司| 浙江风驰机械有限公司| 长江液压机械有限公司| 温州联腾包装机械有限公司| 江苏机械设备有限公司| 衢州 机械有限公司| 三木机械制造实业有限公司| 南通赛孚机械设备有限公司| 西安 机械有限公司| 云南中拓钢铁有限公司| 扬州扬工机械有限公司| 昆明机械制造有限公司| 淮安天宇机械有限公司| 山东莱德机械有限公司| 重庆卡滨通用机械有限公司 | 杭州中亚机械 有限公司| 德清恒丰机械有限公司| 江淮重工机械有限公司| 比富机械(东莞)有限公司| 张家口煤矿机械制造有限公司| 海狮洗涤机械有限公司| 陕西至信机械制造有限公司怎么样 | 长沙远洋机械制造有限公司| 宁波博大机械有限公司| 常州先电机械有限公司| 珠海市广浩捷精密机械有限公司 | 机械设备出口有限公司| 青岛雷沃工程机械有限公司 | 青岛欣鑫数控精密机械有限公司 | 广州海缔机械有限公司| 洛阳洛北重工机械有限公司| 石家庄瑞辉机械设备有限公司 | 洛阳鹏起实业有限公司怎么样| 潍坊圣旋机械有限公司| 无锡市光彩机械制造有限公司| 维美德造纸机械技术有限公司 | 江苏宏光钢铁有限公司| 西安工程机械有限公司| 藏不起服饰有限公司| 佐竹机械苏州有限公司| 台进精密机械有限公司| 海华机械制造有限公司| 江阴市联拓重工机械有限公司 | 河南省时代起重机械有限公司| 大连橡塑机械有限公司| 东莞市台立数控机械有限公司| 协展机械工业有限公司| 山东博杰重型工程机械有限公司 | 扬州海沃机械有限公司| 新乡市金原起重机械有限公司 |