3

我有一張表格,裏面有關於汽車的信息(我們稱之爲tbl_incoming_car)。該表具有一個名爲'customer_number'的非唯一列,顯示目前進入系統的汽車數量。同一輛車可以進出很多次,但是這隻能登記一次。用EF代碼優先鎖定

所以,當一輛新車進場時,我需要得到最後一輛車的號碼,將其增加,然後將其保存爲新車的'客戶號碼'。

我知道最簡單的方法是爲汽車設置一張分開的桌子,在那裏設置'customer_number',然後在其他桌子上註冊,但這僅僅是一個暴露情況的愚蠢示例。所以沒有必要討論這種方法是錯誤的,我知道已經:)

正如我所說,每次有新車進入系統,我必須得到最新添加的行,得到'customer_number ',將其增加並保存爲原子操作。其他應用程序實例可能會嘗試做同樣的事情,並且數據庫必須在「創建任務」期間保存最後添加的行的請求。

我認爲我可以通過將隔離級別設置爲可序列化,但我認爲它不會阻止讀取最後一行,而是阻止從插入新行。所以它看起來像是鎖定解決方案。我已經嘗試在代碼中使用靜態對象作爲監視器,並且它工作正常,但它當然僅限於相同的應用程序域,我需要在數據庫級別的東西。

我不認爲在EF中有什麼東西可以在數據庫上設置鎖定,所以這將是在表格上設置鎖定並稍後釋放的最佳方式?

謝謝。

回答

1

到目前爲止,這是我想出了最好的方式:

public void SetTransactionLock(String resourceName) 
    { 
     Ensure.IsNotNull(resourceName, "resourceName"); 

     String command = String.Format(
     @"declare @result int; 
      EXEC @result = sp_getapplock '{0}', 'Exclusive', 'Transaction', 10000 
      IF @result < 0 
      RAISERROR('ERROR: cannot get the lock [{0}] in less than 10 seconds.', 16, 1);",resourceName); 

     base.Database.ExecuteSqlCommand(command); 
    } 

    public void ReleaseTransactionLock(String resourceName) 
    { 
     Ensure.IsNotNull(resourceName, "resourceName"); 
     String command = String.Format("EXEC sp_releaseapplock '{0}';",resourceName); 
     base.Database.ExecuteSqlCommand(command); 
    } 

由於在EF中沒有內置的方法,我在數據層中添加了這兩個方法,並且我使用它們來聲明只允許一個併發操作的「臨界區」。

您可以在try finally塊中使用它。

+0

謝謝你的-1,但我會愛是理由指出。 – vtortola 2013-04-17 09:35:14

+0

我不能給你理由,但這裏是我的+1而不是 – galets 2014-06-18 16:24:02

+0

謝謝@galets :) – vtortola 2014-06-18 16:24:35

0

EF支持SQL timeStamp,也稱爲rowversion dataType。

這允許您使用樂觀鎖執行更新。 如果這些字段是正確聲明的,框架會爲你做。 本質:

UPDATE其中ID = ID SET CUSTOMER_NUMBER到X + 1 變得 更新其中ID = ID和Rowversion = rowversion集CUSTOMER_NUMBER = X + 1

因此,如果該行已經改變因爲它是在線程X中加載的,所以會出現錯誤。 您可以重新加載並重試,也可以採取在您的方案中採取任何適當的措施。

形式更多信息見,http://msdn.microsoft.com/en-us/data/jj592904

http://msdn.microsoft.com/en-us/library/aa0416cz.aspx

這SO發佈

What is a good method to determine when an entities data has been changed on the database?

+0

我在說插入,而不是更新。沒有人會更新任何行,這只是插入行。 – vtortola 2013-02-21 14:53:25

+0

你怎麼得到一個鎖插入?那麼關鍵的來源是問題。數據庫生成的Key或Guid可以工作。你用什麼來生成「唯一」密鑰。聽起來很奇怪。 – 2013-02-21 14:57:39

+0

使用新教併發,因爲我現在要發佈 – vtortola 2013-02-24 22:44:10

1

可序列化實際上確實解決了這個問題。可序列化意味着事務的行爲就好像它們全部採用全局數據庫X鎖一樣。就好像只有一個事務同時執行一樣。

這對於插入也是如此。這些鎖將以防止插入錯誤位置的方式被採取。

雖然你可能不會實現死鎖。還是值得一試。

+0

我試過了,但遇到了死鎖問題。我不太確定可序列化的作品。例如,如果我在INSERT之前執行SELECT操作以獲取最後一行,是否會在SELECT中阻止讀取以後的行?因爲應用程序讀取最後一行,所以處理該值,然後保存新行。我的理解是,「可序列化」會阻止應用程序插入行,但不能讀取最後一行。 – vtortola 2013-02-24 23:22:37

1

使用REPEATABLE READ隔離級別可以在執行SELECT命令時獲得鎖定。我怎麼不知道然後SELECT命令檢索最後一行,也許它不會工作。執行SELECT的方式會改變SQL鎖定行/索引/表的方式。

最好的違規行爲是執行存儲過程。 EF會在數據庫和應用程序之間進行一些往返操作,這會增加鎖定時間並影響應用程序的性能。

也許你可以在插入後使用觸發器進行更新。

+0

沒錯,但我們試圖在沒有SP的情況下,直到我們已經清楚所有我們必須做的事情(動態需求,你知道:P)。目前我們經歷了重構的大循環,SP會阻礙。 – vtortola 2013-03-04 14:57:53

+0

EF不會給你很多選擇來控制SQL上的事務。在過去,我只用了EF,但爲了表演,我被迫違反了規則。在整個應用程序中,只有大約5個SP,用於非常特定的性能密集型數據事務。 – 2013-03-06 01:12:31

1

EF使用C#TransactionScope

在過去,我一直在解決類似的問題是這樣的:

using (var ts = new TransactionScope()) 
     { 
      using (var db = new DbContext()) 
      { 
       db.Set<Car>().Add(newCar); 
       db.SaveChanges(); 
       // newCar.Id has value here, but record is locked for read until ts.Complete() 

       newCar.CustomerNumber = db.Set<Car>().Max(car => car.CustomerNumber) + 1; 
       db.SaveChanges(); 
      } 
      ts.Complete(); 
     } 

任何併發任務線db.Set<Car>().Max(car => car.CustomerNumber)將不得不等待,因爲它需要能夠訪問所有的記錄,你可以檢查此通過在ts.Complete()之前添加斷點並在該行暫停代碼時嘗試執行Select max(CustomerNumber) from dbname.dbo.Cars。當您恢復代碼並完成ts時,查詢將完成。

當然,這隻適用於你的例子足夠好描述你的真實場景,但它應該很容易適應你的需求。

查看transactions in ef on MSDN瞭解更多信息。

+0

看到雖然有一個事務,但我需要阻止其他線程讀取數據,而不能用簡單的事務完成。 「可序列化」隔離級別中的事務將阻止其他線程添加匹配給定謂詞的行,但不會讀取它。 – vtortola 2013-04-17 09:33:11

+0

我不明白。像這樣的默認事務將阻止其他線程讀取正在添加的行(newCar),並且如果只使用此方法插入記錄,則它們將等待事務處理完成以便能夠讀取該記錄,並添加一個新的。 – 2013-04-17 17:57:50

1

以前在此線程中提到的解決方案可能不是普遍適用的,因爲EF在任何SQL操作之前運行sp_resetconnection,所以當您在DbContext上運行任何invoiving SQL時,它本質上會釋放您應該保留的鎖。

我想出是:你需要獲得鎖定之前,克隆的SqlConnection,並按住該連接,直到您準備發佈:

public class SqlAppLock { 
    DbConnection connection; 
    public SqlAppLock(DbContext context, string resourceName) 
    { 
     ... (test arguments for null, etc) 
     connection = (DbConnection)(context.Database.Connection as ICloneable).Clone(); 
     connection.Open(); 

     var cmd = connection.CreateCommand(); 
     cmd.CommandText = "sp_getapplock"; 
     ... (set up parameters) 
     cmd.ExecuteNonQuery(); 

     int result = (int)cmd.Parameters["@result"].Value; 
     if (result < 0) 
     { 
      throw new ApplicationException("Could not acquire lock on resource"); 
     } 
    } 

,然後釋放,可以解除鎖定使用sp_releaseapplock,或簡單地Dispose()連接