參考頁面: http://www.yuanjiaocheng.net/entity/entity-relations.html http://www.yuanjiaocheng.net/entity/entity-lifecycle.html http://www.yuanjiaocheng.net ...
參考頁面:
http://www.yuanjiaocheng.net/entity/entity-relations.html
http://www.yuanjiaocheng.net/entity/entity-lifecycle.html
http://www.yuanjiaocheng.net/entity/code-first.html
http://www.yuanjiaocheng.net/entity/mode-first.html
http://www.yuanjiaocheng.net/entity/database-first.html
什麼是併發?
併發分悲觀併發和樂觀併發。
悲觀併發:比如有兩個用戶A,B,同時登錄系統修改一個文檔,如果A先進入修改,則系統會把該文檔鎖住,B就沒辦法打開了,只有等A修改完,完全退出的時候B才能進入修改。
樂觀併發:同上面的例子,A,B兩個用戶同時登錄,如果A先進入修改緊跟著B也進入了。A修改文檔的同時B也在修改。如果在A保存之後B再保存他的修改,此時系統檢測到資料庫中文檔記錄與B剛進入時不一致,B保存時會拋出異常,修改失敗。
EF中如何控制併發?
Entity Framework不支持悲觀併發,只支持樂觀併發。
如果要對某一個表做併發處理,就在該表中加一條Timestamp類型的欄位。註意,一張表中只能有一個Timestamp的欄位。
Data Annotations中用Timestamp來標識設置併發控制欄位,標識為Timestamp的欄位必需為byte[]類型。
public class Person { public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [Timestamp] public byte[] RowVersion { get; set; } }
Fluent API用IsRowVersion方法
modelBuilder.Entity<Person>().Property(p => p.RowVersion).IsRowVersion();
我們看到生成的資料庫中,RowVersion是timestamp類型。
下麵我們寫一段代碼來測試一下:
static void Main(string[] args) { var person = new Person { FirstName = "Rowan", LastName = "Miller", SocialSecurityNumber = 12345678 }; //新增一條記錄,保存到資料庫中 using (var con = new BreakAwayContext()) { con.People.Add(person); con.SaveChanges(); } var firContext = new BreakAwayContext(); //取第一條記錄,並修改一個欄位:這裡是修改了FirstName //先不保存 var p1 = firContext.People.FirstOrDefault(); p1.FirstName = "Steven"; //再創建一個Context,同樣取第一條記錄,修改LastName欄位並保存 using (var secContext = new BreakAwayContext()) { var p2 = secContext.People.FirstOrDefault(); p2.LastName = "Francis"; secContext.SaveChanges(); } try { firContext.SaveChanges(); Console.WriteLine(" 保存成功"); } catch (DbUpdateConcurrencyException ex) { Console.WriteLine(ex.Entries.First().Entity.GetType().Name + " 保存失敗"); } Console.Read(); }
上面我們實例化了三個DbContext,第一個增加一條記錄到資料庫中,第二個修改剛增加的記錄但不保存,然後第三個Context也取剛新增的記錄並保存,最後再保存第二個Context,結果保存失敗。
可以看到我們的併發控制取到了作用。
分析EF生成的SQL語句:
exec sp_executesql N'update [dbo].[People] set [LastName] = @0 where (([PersonId] = @1) and ([RowVersion] = @2)) select [RowVersion] from [dbo].[People] where @@ROWCOUNT > 0 and [PersonId] = @1',N'@0 nvarchar(max) ,@1 int,@2 binary(8)',@0=N'Francis',@1=1,@2=0x00000000000007D1
可以看到,它在取對應記錄的時候把RowVersion也作為篩選條件。上面例子中的secContext保存的時候,資料庫中的RowVersion欄位的值就變了,所以firContext保存的時候用原來的RowVersion取值,自然就取不到相應的記錄而報錯。
如果我們只是要對某個欄位作併發控制呢?彆著急,EF也有辦法。
Data Annotations中用ConcurrencyCheck來標識
public class Person { public int PersonId { get; set; } [ConcurrencyCheck] public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public byte[] RowVersion { get; set; } }
Fluent API用IsConcurrencyToken方法
modelBuilder.Entity<Person>().Property(p => p.SocialSecurityNumber).IsConcurrencyToken();
上面的實體中,我們將SocialSecurityNumber(社會保險號)標識為開放式併發,也寫一個類似的代碼測試一下:
static void Main(string[] args) { var person = new Person { FirstName = "Rowan", LastName = "Miller", SocialSecurityNumber = 12345678 }; //新增一條記錄,保存到資料庫中 using (var con = new BreakAwayContext()) { con.People.Add(person); con.SaveChanges(); } var firContext = new BreakAwayContext(); //取第一條記錄,並修改SocialSecurityNumber欄位 //先不保存 var p1 = firContext.People.FirstOrDefault(); p1.SocialSecurityNumber = 123; //再創建一個Context,同樣取第一條記錄, //修改SocialSecurityNumber欄位並保存 using (var secContext = new BreakAwayContext()) { var p2 = secContext.People.FirstOrDefault(); p2.SocialSecurityNumber = 456; secContext.SaveChanges(); } try { firContext.SaveChanges(); Console.WriteLine(" 保存成功"); } catch (DbUpdateConcurrencyException ex) { Console.WriteLine(ex.Entries.First().Entity.GetType().Name + " 保存失敗"); } Console.Read(); }
運行結果同樣是保存失敗,說明我們的併發控制起作用了。
分析一下EF執行的SQL:
exec sp_executesql N'update [dbo].[People] set [SocialSecurityNumber] = @0 where (([PersonId] = @1) and ([SocialSecurityNumber] = @2)) ',N'@0 int,@1 int,@2 int',@0=123,@1=1,@2=12345678
可以看到,EF將我們要併發控制的列SocialSecurityNumber也作為一個篩選條件,這樣firContext保存的時候也會因為的資料庫中SocialSecurityNumber值變了,取不到對應的記錄而更新失敗。
補充一下:如果是EDMX如何將欄位設置為Concurrency。很簡單,在對應的欄位上右鍵-屬性。在打開的屬性視窗中有一個併發模式,你將它選擇為Fixed即可。