前面的6篇博客基本把Code First學習的差不多了,今天這篇學習下code first中的並發控制和事務,基本也快學完了,頂多就差數據遷移。
在數據庫中也是有鎖和事務的概念,在C#中也是存在,當然code first也是必要要有的。對於什么是並發、什么是鎖和事務,它們的特性是什么這些概念性的就不一一列舉。因為這些要是發散的學習就涉及到好多的知識點。並發能聯想到多線程,多線程能聯想到同步異步,同步異步操作系統,等等。知識都是連貫着的。有了並發那就有不並發的情況,想讓不並發,那用什么呢?那就要用鎖。說起鎖又想起鎖的類型、粒度、死鎖等。什么悲觀鎖、樂觀鎖、粒度上又分單元格級、行級、表級等。總的來說想弄明白C#是很不容易的,擔負着打光棍的風險,把青春放在了敲代碼上。上面算是開場白一些不正經的,下面開始說正經的。
一、並發控制
1.鎖分悲觀鎖和樂觀鎖。
在code first中使用的是樂觀鎖。至於什么是悲觀鎖,大家應該都用過版本控制軟件,VSS中有簽入簽出,當被遷出的時候其他人是修改不了的,這種就是可以理解為悲觀鎖。而在svn中當一個文件被遷出時,其他人還可以修改,之前commit的時候根據版本合並就OK,這種可以理解為樂觀鎖。
2.鎖的粒度
code first在鎖的粒度上也分為兩種。一是行級一個單元格級及屬性集。
1).行級
使用Timestamp來控制每行的並發。一張表中只能有一個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; } public decimal Money { get; set; } [Timestamp] public byte[] RowVersion { get; set; } }
var person = new Person { FirstName = "Cui", LastName = "YanWei", SocialSecurityNumber = 12345678 }; //新增一條記錄,保存到數據庫中 using (var con = new EFCodeFirstDbContext()) { con.Persons.Add(person); con.SaveChanges(); } var firContext = new EFCodeFirstDbContext(); //取第一條記錄,並修改一個字段:這里是修改了FirstName //先不保存 var p1 = firContext.Persons.FirstOrDefault(); p1.FirstName = "CuiA"; //再創建一個Context,同樣取第一條記錄,修改LastName字段並保存 using (var secContext = new EFCodeFirstDbContext()) { var p2 = secContext.Persons.FirstOrDefault(); p2.LastName = "Ivan"; secContext.SaveChanges(); } try { firContext.SaveChanges(); Console.WriteLine(" 保存成功"); } catch (DbUpdateConcurrencyException ex) { Console.WriteLine(ex.Entries.First().Entity.GetType().Name + " 保存失敗"); } Console.Read();
在上面的代碼中首先是新增一個Person對象,SaveChanges()保存到數據庫。然后將Person對象獲取出來改變FirstName,但此時並沒有SaveChanges()保存到數據庫.然后呢有將該對象取出來,修改LastName並SaveChanges()保存到數據庫,此時再將上面的修改FirstName的保存到數據庫,運行時會拋出異常。這是為什么呢?
可以看下下面的三個截圖:
上面的3個截圖,第一個sql是最先執行,然后是第二個,不過這里要注意下兩個sql的binary字段,兩個都是一樣的,但是最后存在數據庫的可是不一樣,其實當第一個sql執行完時binary字段變成了也就是數據庫現在存在的值,但是第二個sql執行時還是用的之前的值,所以就更新失敗拋出異常。這就是Timestamp的機制。
2).列級
Data Annotations中用ConcurrencyCheck來標識控制列的並發。此時可以先把上面的Person類中的Timestamp注釋掉。在SocialSecurityNumber屬性上添加約定。
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 decimal Money { get; set; } //[Timestamp] public byte[] RowVersion { get; set; } }
我們將SocialSecurityNumber(社會保險號)標識為開放式並發。
var person = new Person { FirstName = "Cui", LastName = "YanWei", SocialSecurityNumber = 12345678 }; //新增一條記錄,保存到數據庫中 using (var con = new EFCodeFirstDbContext()) { con.Persons.Add(person); con.SaveChanges(); } var firContext = new EFCodeFirstDbContext(); //取第一條記錄,並修改一個字段:這里是修改了SocialSecurityNumber=123 //先不保存 var p1 = firContext.Persons.FirstOrDefault(); p1.SocialSecurityNumber = 123; //再創建一個Context,同樣取第一條記錄,修改SocialSecurityNumber=456並保存 using (var secContext = new EFCodeFirstDbContext()) { var p2 = secContext.Persons.FirstOrDefault(); p2.SocialSecurityNumber = 456; secContext.SaveChanges(); } try { firContext.SaveChanges(); Console.WriteLine(" 保存成功"); } catch (DbUpdateConcurrencyException ex) { Console.WriteLine(ex.Entries.First().Entity.GetType().Name + " 保存失敗"); } Console.Read();
下面還是3張截圖.
和Timestamp類似,3個截圖的第一個sql是第二個更改操作的數據庫上下文更改執行的sql,第二個是第一個更改操作的數據庫上下文更改執行的sql,where語句中增加了SocialSecurityNumber,由於第一個sql執行之后SocialSecurityNumber已經改變,第二個sql還是使用舊的SocialSecurityNumber,所以更新失敗。
二、事務
1.默認事務處理
EF的默認行為是,無論何時執行任何涉及Create,Update或Delete的查詢,都會默認創建事務。當DbContext類上的SaveChanges()方法被調用時,事務就會提交。
2.分布式事務
對於分布式事務可以想到TransactionScope,code first中分布式事務意味着多個DbContext,由於EF默認就是事務處理,所以只需關注分布式的事務,對於分布式事務的應用需要開啟window服務,這個日后會具體總結。注意要引入類庫:
using System.Transactions;
using (var ts = new TransactionScope(TransactionScopeOption.Required)) { //db數據庫上下文操作 ts.Complete(); }
3.EF6管理事務
從EF 6起,EF在DbContext對象上提供了Database.BeginTransaction()
方法,當使用上下文類在事務中執行原生SQL命令時,這個方法特別有用。
using (var db = new EFCodeFirstDbContext()) { using (var trans = db.Database.BeginTransaction()) { try { //執行sql db.SaveChanges(); trans.Commit(); } catch (Exception ex) { trans.Rollback(); } } }