本篇目錄
本系列的源碼本人已托管於Coding上:點擊查看,想要注冊Coding的可以點擊該連接注冊。
先附上codeplex上EF的源碼:entityframework.codeplex.com,此外,本人的實驗環境是VS 2013 Update 5,windows 10,MSSQL Server 2008/2012。
理解並發
並發管理解決的是允許多個實體同時更新,實際上這意味着允許同時在相同的數據上執行多個數據庫操作。並發是在一個數據庫上管理多個操作的一種方式,同時遵守了數據庫操作的ACID屬性(原子性,一致性,隔離性和持久性)。
想象一下下面幾種可能發生並發的場景:
- 用戶甲和乙都嘗試修改相同的實體
- 用戶甲和乙都嘗試刪除相同的實體
- 用戶甲正在嘗試修改一個實體時,用戶乙已經刪除了該實體
- 用戶甲已經請求讀取一個實體,用戶乙讀完該實體之后更新了它
這些場景可能會潛在地產生錯誤的數據,試想,成百上千的用戶同時嘗試操作一個相同的實體,這種並發問題將會對系統帶來更大的影響。
在處理與並發相關的問題時,一般有兩種方法:
- 積極並發:無論何時從數據庫請求數據,數據都會被讀取並保存到應用內存中。數據庫級別沒有放置任何顯式鎖。數據操作會按照數據層接收到的順序執行。
- 消極並發:無論何時從數據庫請求數據,數據都會被讀取,然后該數據上就會加鎖,因此沒有人能訪問該數據。這會降低並發相關問題的機會,缺點是加鎖是一個昂貴的操作,會降低整個應用程序的性能。EF默認支持積極並發,這樣,一旦所有的數據被讀取,就會呈現在內存中。當然,也可以配置EF使用消極並發,但是EF不直接支持。
理解積極並發
前面提到,在積極並發中,無論何時從數據庫請求數據,數據都會被讀取並保存到應用內存中。數據庫級別沒有放置任何顯式鎖。因為這種方法沒有添加顯式鎖,所以比消極並發更具擴展性和靈活性。使用積極並發,重點是如果發生了任何沖突,應用程序要親自處理它們。最重要的是,使用積極並發控制時,在應用中要有一個沖突處理策略,要讓應用程序的用戶知道他們的修改是否因為沖突的緣故沒有持久化。積極並發本質上是允許沖突發生,然后以一種適當的方式解決該沖突。
下面是處理沖突的策略例子。
忽略沖突/強制更新
這種策略是讓所有的用戶更改相同的數據集,然后所有的修改都會經過數據庫,這就意味着數據庫會顯示最后一次更新的值。這種策略會導致潛在的數據丟失,因為許多用戶的更改都丟失了,只有最后一個用戶的更改是可見的。
部分更新
在這種情況中,我們也允許所有的更改,但是不會更新完整的行,只有特定用戶擁有的列更新了。這就意味着,如果兩個用戶更新相同的記錄但卻不同的列,那么這兩個更新都會成功,而且來自這兩個用戶的更改都是可見的。
警告/詢問用戶
當一個用戶嘗試更新一個記錄時,但是該記錄自從他讀取之后已經被別人修改了,這時應用程序就會警告該用戶該數據已經被某人更改了,然后詢問他是否仍然要重寫該數據還是首先檢查已經更新的數據。
拒絕更改
當一個用戶嘗試更新一個記錄時,但是該記錄自從他讀取之后已經被別人修改了,此時告訴該用戶不允許更新該數據,因為數據已經被某人更新了。
理解消極並發
和積極並發相反,消極並發的目標是永遠不讓任何沖突發生。這是通過在使用記錄之前就在記錄上放置顯式鎖實現的。數據庫記錄上可以得到兩種類型的鎖:
- 只讀鎖
- 更新鎖
當把只讀鎖放到記錄上時,應用程序只能讀取該記錄。如果應用程序要更新該記錄,它必須獲取到該記錄上的更新鎖。如果記錄上加了只讀鎖,那么該記錄仍然能夠被想要只讀鎖的請求使用。然而,如果需要更新鎖,該請求必須等到所有的只讀鎖釋放。同樣,如果記錄上加了更新鎖,那么其他的請求不能再在這個記錄上加鎖,該請求必須等到已存在的更新鎖釋放才能加鎖。
從前面的描述中,似乎消極並發能解決所有跟並發相關的問題,因為我們不必在應用中處理這些問題。然而,事實上並不是這樣的。在使用消極並發管理之前,我們需要記住,使用消極並發有很多問題和開銷。下面是使用消極並發面臨的一些問題:
- 應用程序必須管理每個操作正在獲取的所有鎖;
- 加鎖機制的內存需求會降低應用性能
多個請求互相等待需要的鎖,會增加死鎖的可能性。由於這些原因,EF不直接支持消極並發。如果想使用消極並發的話,我們可以自定義數據庫訪問代碼。此外,當使用消極並發時,LINQ to Entities不會正確工作。
我們盡可能不要嘗試使用消極並發。並發相關的沖突可以使用
TimeStamp
字段或者RowVersion
類型處理。后面會做介紹。
使用EF實現積極並發
使用EF實現積極並發有很多方法,接下來我們就會看一下這些方法。我這里仍然使用打賞者的例子。
新建一個控制台項目,取名ConcurrencyAndTransactionManagement,這次只創建打賞者實體類如下:
public class Donator
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Amount { get; set; }
public DateTime DonateDate { get; set; }
}
EF的默認並發
先看一下EF默認是如何處理並發的。現在假設我們的應用程序要更新一個Donator的Amount值,那么我們首先需要實現這兩個函數GetDonator() 和 UpdateDonator(),前者用於獲取指定Donator,后者用於更新指定Donator。
static Donator GetDonator(int id)
{
using (var db=new Context())
{
return db.Donators.Find(id);
}
}
static void UpdateDonator(Donator donator)
{
using (var db=new Context())
{
db.Entry(donator).State=EntityState.Modified;
db.SaveChanges();
}
}
下面我們實現這么一種場景:有兩個用戶甲和乙都讀取了同一個Donator實體,然后這兩個用戶都嘗試更新這個實體的不同字段,比如甲更新Name字段,乙更新Amount字段,代碼如下:
//1.用戶甲獲取id=1的打賞者
var donator1 = GetDonator(1);
//2.用戶乙也獲取id=1的打賞者
var donator2 = GetDonator(1);
//3.用戶甲只更新這個實體的Name字段
donator1.Name = "用戶甲";
UpdateDonator(donator1);
//4.用戶乙只更新這個實體的Amount字段
donator2.Amount = 100m;
UpdateDonator(donator2);
上面的代碼嘗試模擬了一種並發問題。現在,兩個用戶都有相同的數據副本,然后嘗試更新相同的記錄。執行代碼前,先看下數據庫中的數據:
為了測試,在執行第四步時打一個斷點:
在斷點之后的代碼執行之前,去數據庫看一下數據,可以看到用戶甲的更新已經產生作用了:
繼續執行代碼,再看一下數據庫中的數據發生了什么變化:
從上面的截圖可以看出,用戶乙的請求成功了,而用戶甲的更新丟失了。因此,從上面的代碼不難看出,如果我們使用EF更新整條記錄,那么最后一個請求總會獲取勝利,也就是說,最后一次請求的更新會覆蓋之前所有請求的更新。
設計處理字段級別並發的應用
接下來,我們會看到如何編寫處理字段級並發問題的應用代碼。這種方式設計應用的思想是,只有更新的字段會在數據庫中更改。這個就保證了如果多個用戶正在更新不同的字段,所有的更改都會持久化到數據庫。
實現這個的關鍵是讓該應用識別用戶正在請求更新的所有列,然后為該用戶有選擇地更新那些字段。通過以下兩個東西來實現:
- 一個方法:該方法會給我們一個原始模型的克隆,只有用戶請求的屬性會更新為新值
- 更新方法:它會檢查原始請求模型的哪個屬性值已經更改,然后在數據庫中只更新那些值。
因此,首先需要創建一個簡單的方法,該方法需要模型屬性的值,然后會返回一個新的模型,該模型除了用戶嘗試更新的屬性之外,其他的屬性值都和原來的模型屬性值相同。
static Donator GetUpdatedDonator(int id,string name,decimal amount,DateTime donateDate)
{
return new Donator
{
Id = id,
Name = name,
Amount = amount,
DonateDate = donateDate
};
}
如果用戶只想更新Amount
字段,方法的調用就像下面這樣:
var donator1 = GetDonator(1);
var donator2 = GetDonator(1);
var newDonator = GetUpdatedDonator(donator2.Id, donator1.Name,100m, donator1.DonateDate);
在上面的代碼中,donator1是用戶請求模型的原始對象,100m是打賞金額的新值。
上面的方法超級簡單,它只顯示了如何獲得具有更新屬性值的克隆對象。現實生活中,很少會看到這樣的代碼。為了更簡潔,我們還可以使用映射模塊將領域模型映射到數據模型。
下一步,需要更改更新方法。該更新方法會實現下面更新數據的算法:
- 從數據庫中檢索最新的模型值
- 檢查原始模型和要更新的模型來找出更改屬性的列表
- 只更新步驟1中檢索到的模型發生變化的屬性
- 保存更改
該算法的代碼大概像下面這個樣子:
static void UpdateDonatorEnhanced(Donator originalDonator,Donator newDonator)
{
using (var db=new Context())
{
//從數據庫中檢索最新的模型
var donator = db.Donators.Find(originalDonator.Id);
//接下來檢查用戶修改的每個屬性
if (originalDonator.Name!=newDonator.Name )
{
//將新值更新到數據庫
donator.Name = newDonator.Name;
}
if (originalDonator.Amount != newDonator.Amount)
{
//將新值更新到數據庫
donator.Amount = newDonator.Amount;
}
//這里省略其他屬性...
db.SaveChanges();
}
}
接下來,使用這兩個方法來更新應用程序代碼,並檢查結果:
#region 2.0 設計處理字段級別的並發應用
//1.用戶甲讀取id=1的打賞者
var donator1 = GetDonator(1);
//2.用戶乙同樣讀取id=1的打賞者
var donator2 = GetDonator(1);
//3.用戶甲通過創建一個新的對象來更新打賞金額為100m
var newDonator1 = GetUpdatedDonator(donator2.Id, donator1.Name,100m, donator1.DonateDate);
UpdateDonatorEnhanced(donator1,newDonator1);
//4.用戶乙通過創建一個新的對象來更新打賞者姓名為“並發測試”
var newDonator2 = GetUpdatedDonator(donator2.Id, "並發測試", donator2.Amount, donator2.DonateDate);
UpdateDonatorEnhanced(donator1, newDonator2);
#endregion
運行代碼之前,先看下數據庫中的數據:
在執行第四步時打個斷點,運行程序:
再次查看數據庫中的數據,發現用戶甲的操作已經執行了:
繼續運行程序,再次查看數據庫的數據,發現用戶乙的操作也執行了:
從上面的截圖看到,兩個用戶的請求同一個實體的更新值都持久化到數據庫中了。因此,如果用戶更新不同的字段,該程序可以有效地處理並發更新了。但是如果多個用戶同時更新相同的字段,那么這種方法仍然顯示的是最后一次請求的值。雖然這種方式減少了一些並發相關的問題,但是這種方法意味着我們必須寫大量代碼來處理並發問題。后面我們會看到如何使用EF提供的機制來處理並發問題。
為並發實現RowVersion
前面,我們看到了EF默認如何處理並發(最后一次請求獲勝),然后看了如果多個用戶嘗試更新不同的字段時,如何設計應用處理這些問題。接下來,我們看一下當多個用戶更新相同的字段時,使用EF如何處理字段級並發。
EF讓我們指定字段級並發,這樣如果一個用戶更新一個字段的同時,該字段已經被其他人更新過了,就會拋出一個並發相關的異常。使用這種方法,當多個用戶嘗試更新相同的字段時,我們就可以更有效地處理並發相關的問題。
如果我們為多個字段使用了特定字段的並發,那么會降低應用性能,因為生成的Sql會更大,更加有效的方式就是使用RowVersion機制。RowVersion機制使用了一種數據庫功能,每當更新行的時候,就會創建一個新的行值。
給Donator實體添加一個屬性:
[Timestamp]
public byte[] RowVersion { get; set; }
//修改上下文
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Donator>().Property(d => d.RowVersion).IsRowVersion();
base.OnModelCreating(modelBuilder);
}
添加一個數據庫初始化器,重新生成數據庫,數據庫模式變為:
插入兩條數據,RowVersion列顯示的是二進制數據:
現在,EF就會為並發控制追蹤RowVersion列值。接下來嘗試更新不同的列:
//1.用戶甲獲取id=1的打賞者
var donator1 = GetDonator(1);
//2.用戶乙也獲取id=1的打賞者
var donator2 = GetDonator(1);
//3.用戶甲只更新這個實體的Name字段
donator1.Name = "用戶甲";
UpdateDonator(donator1);
//4.用戶乙只更新這個實體的Amount字段
donator2.Amount = 100m;
UpdateDonator(donator2);
運行程序,會拋出下面的異常:
其他信息:Entities may have been modified or deleted since entities were loaded.
從拋出的異常信息來看,很明顯是拋出了和並發相關的異常DbUpdateConcurrencyException
,其他信息說明了自從實體加載以來,可能已經被修改或刪除了
。
無論何時一個用戶嘗試更新一條已經被其他用戶更新的記錄,都會獲得異常DbUpdateConcurrencyException
。
當實現並發時,我們總要編寫異常處理的代碼,給用戶展示一個更友好的描述信息。比如:
//4.用戶乙只更新這個實體的Amount字段
try
{
donator2.Amount = 100m;
UpdateDonator(donator2);
Console.WriteLine("應該發生並發異常!");
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine("異常如願發生!");
}
此時,我們應該使用當前的數據庫值更新數據,然后重新更改。作為開發者,如果我們想要協助用戶的話,可以使用EF的DbEntityEntry
類獲取當前的數據庫值。
理解事務
處理以數據為中心的應用時,另一個重要的話題是事務管理。ADO.NET為事務管理提供了一個非常干凈和有效的API。因為EF運行在ADO.NET之上,所以EF可以使用ADO.NET的事務管理功能。
當從數據庫角度談論事務時,它意味着一系列操作被當作一個不可分割的操作。所有的操作要么全部成功,要么全部失敗。事務的概念是一個可靠的工作單元,事務中的所有數據庫操作應該應該被看作一個工作單元。
從應用程序的角度來看,如果我們有多個數據庫操作被當作一個工作單元,那么應該將這些操作包裹在一個事務中。為了能夠使用事務,應用程序需要執行下面的步驟:
- 開始事務;
- 執行所有的查詢,執行所有的數據庫操作,這些操作被視為一個工作單元;
- 如果所有的事務成功了,那么提交事務;
- 如果任何一個操作失敗,就回滾事務。
創建測試環境
提到事務,最經典的例子莫過於銀行轉賬了。我們這里也使用這個例子來理解一下和事務相關的概念。為了簡單模擬銀行轉賬的情景,假設銀行為不同的賬戶類型使用了不同的表,對應地,我創建了兩個實體OutputAccount
和InputAccount
,實體類代碼如下:
[Table("OutputAccounts")]
public class OutputAccount
{
public int Id { get; set; }
[StringLength(8)]
public string Name { get; set; }
public decimal Balance { get; set; }
}
[Table("InputAccounts")]
public class InputAccount
{
public int Id { get; set; }
[StringLength(8)]
public string Name { get; set; }
public decimal Balance { get; set; }
}
從應用程序的角度看,無論何時用戶將錢從OutputAccount
轉入InputAccount
,這個操作應該被視為一個工作單元。永遠不應該發生OutputAccount的金額扣除了,而InputAccount的金額沒有增加!接下來我們就看一下使用EF如何管理事務。
現在給數據庫插入數據,讓它們的初始金額如下所示:
現在,我們嘗試使用EF的事務從OutputAccount
的甲轉入1000給InputAccount
的乙。
這只是個例子,只為了以一種簡單的方式描述涉及到的概念。現實生活中,這個場景的數據庫會遠比這個更復雜和更優化。
EF的默認事務處理
EF的默認行為是,無論何時執行任何涉及Create,Update或Delete的查詢,都會默認創建事務。當DbContext類上的SaveChanges()
方法被調用時,事務就會提交。
要實現我們的場景,代碼應該是下面這樣的:
#region 4.0 EF默認的事務處理
int outputId = 2,inputId=1;
decimal transferAmount = 1000m;
using (var db=new Context())
{
//1 檢索事務中涉及的賬戶
var outputAccount = db.OutputAccounts.Find(outputId);
var inputAccount = db.InputAccounts.Find(inputId);
//2 從輸出賬戶上扣除1000
outputAccount.Balance -= transferAmount;
//3 從輸入賬戶上增加1000
inputAccount.Balance += transferAmount;
//4 提交事務
db.SaveChanges();
}
#endregion
運行程序,結果如下:
可以看到,甲賬戶上少了1000,而乙賬戶上多了1000。因此,這兩個操作有效地被包裹在了一個事務中,並作為一個工作單元執行。如果任何一個操作失敗,數據就不會發生變化。
因為把讀操作放到事務中沒有好處,但是卻降低了整個應用程序的性能,因此,EF不會對涉及數據庫的
Select
查詢使用事務。
使用TransactionScope處理事務
如果有一個場景具有多個DbContext對象,那么我們想將涉及多個DbContext對象的操作關聯為一個工作單元,這時,我們需要在TransactionScope
對象內部包裹SaveChanges
方法的調用。為了描述這個場景,我們使用DbContext類的兩個不同實例來執行扣款和收款:
#region 5.0 使用TransactionScope處理事務
int outputId = 2, inputId = 1;
decimal transferAmount = 1000m;
using (var ts=new TransactionScope(TransactionScopeOption.Required))
{
var db1=new Context();
var db2=new Context();
//1 檢索事務中涉及的賬戶
var outputAccount = db1.OutputAccounts.Find(outputId);
var inputAccount = db2.InputAccounts.Find(inputId);
//2 從輸出賬戶上扣除1000
outputAccount.Balance -= transferAmount;
//3 從輸入賬戶上增加1000
inputAccount.Balance += transferAmount;
db1.SaveChanges();
db2.SaveChanges();
ts.Complete();
}
#endregion
上面的代碼中,我們使用了兩個不同的DbContext實例執行扣款和收款操作。因此,默認的EF行為不會工作。在調用各自的SaveChanges()
方法時,和上下文相關的各個事務不會提交。相反,因為它們都在TransactionScope
對象的內部,所以,當TransactionScope
對象的Complete()
方法調用時,事務才會提交。如果任何一個操作失敗,就會發生異常,TransactionScope就不會調用Complete()
方法,從而回滾更改。
使用EF6管理事務
從EF 6起,EF在DbContext對象上提供了Database.BeginTransaction()
方法,當使用上下文類在事務中執行原生SQL命令時,這個方法特別有用。
接下來看一下如何使用這個新方法管理事務。這里我們使用原生SQL從OutputAccounts
中扣款,使用模型類給InputAccounts
收款:
#region 6.0 使用EF6管理事務
int outputId = 2, inputId = 1;
decimal transferAmount = 1000m;
using (var db=new Context())
{
using (var trans=db.Database.BeginTransaction())
{
try
{
var sql = "Update OutputAccounts set Balance=Balance-@amountToDebit where id=@outputId";
db.Database.ExecuteSqlCommand(sql, new SqlParameter("@amountToDebit", transferAmount), new SqlParameter("@outputId",outputId));
var inputAccount = db.InputAccounts.Find(inputId);
inputAccount.Balance += transferAmount;
db.SaveChanges();
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
}
}
}
#endregion
稍作解釋,首先創建了一個DbContext類的實例,然后使用這個實例通過調用Database.BeginTransaction()
方法開始了一個事務。該方法給我們返回了一個DbContextTransaction
對象的句柄,使用該句柄可以提交或者回滾事務。然后使用原生SQL從OutputAccounts中扣款,使用模型類為 InputAccounts收款。調用SaveChanges()
方法只會影響第二個操作(在事務提交之后影響),但不會提交事務。如果兩個操作都成功了,那么就調用DbContextTransaction
對象的Commit()
方法,否則,我們就處理異常並調用Rollback()
方法回滾事務。
這種方式只用於EF6,如果是EF6之前的版本,必須依賴
TransactionScope
管理事務。
使用已存在的事務
有時,我們想在EF的DbContext類中使用一個已存在的事務。原因可能有這么幾個:
- 一些操作可能在應用的不同部分完成。
- 對老項目使用了EF,並且這個老項目使用了一個類庫,這個類庫給我們提供了事務或數據庫連接的句柄。
對於這些場景,EF允許我們在DbContext類中使用一個和事務相關聯的已存在連接。接下來,寫一個簡單的函數來模擬老項目的類庫提供句柄,該函數使用純粹的ADO.NET執行扣款操作:
//模擬老項目的類庫
static bool DebitOutputAccount(SqlConnection conn, SqlTransaction trans, int accountId, decimal amountToDebit)
{
int affectedRows = 0;
var command = conn.CreateCommand();
command.Transaction = trans;
command.CommandType=CommandType.Text;
command.CommandText = "Update OutputAccounts set Balance=Balance-@amountToDebit where id=@accountId";
command.Parameters.AddRange(new SqlParameter[]
{
new SqlParameter("@amountToDebit",amountToDebit),
new SqlParameter("@accountId",accountId)
});
try
{
affectedRows= command.ExecuteNonQuery();
}
catch (Exception ex)
{
throw ex;
}
return affectedRows == 1;
}
該函數需要四個參數,來自調用者的數據庫連接對象和事務對象,以及扣款賬戶id和扣款金額,知道了這些參數之后就可以執行扣款操作的更新查詢。
現在,假設這個函數以類庫的行為提供給我們,很顯然,我們什么都不能更改。這種情況,我們不能使用Database.BeginTransaction
方法,因為我們需要將SqlConnection
和SqlTransaction
對象傳給該函數,並把該函數放到我們的事務里。這樣,我們就需要首先創建一個SqlConnection
,然后開始SqlTransaction
。代碼如下:
#region 7.0 使用已存在的事務
int outputId = 2, inputId = 1;
decimal transferAmount = 1000m;
var connectionString = ConfigurationManager.ConnectionStrings["ConcurrencyAndTransactionManagementConn"].ConnectionString;
using (var conn=new SqlConnection(connectionString))
{
conn.Open();
using (var trans=conn.BeginTransaction())
{
try
{
var result = DebitOutputAccount(conn, trans, outputId, transferAmount);
if (!result)
{
throw new Exception("不能正常扣款!");
}
using (var db=new Context(conn,contextOwnsConnection:false))
{
db.Database.UseTransaction(trans);
var inputAccount=db.InputAccounts.Find(inputId);
inputAccount.Balance += transferAmount;
db.SaveChanges();
}
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
}
}
}
#endregion
稍作解釋,首先創建了一個SqlConnection
,然后使用該連接關聯了一個SqlTransaction
。事務開始后,我們就使用連接和事務對象調用老項目中的方法,然后檢查了一下調用老項目中的方法是否執行成功!如果失敗,我們直接拋出異常,捕獲異常后會回滾該事務。如果成功了,我們使用了DbContext類來為InputAccounts
用戶添加收款,並提交事務。這里有一句代碼值得注意db.Database.UseTransaction(trans);
,這句話的意思是,EF執行的操作都在外部傳入的事務中執行。還有,contextOwnsConnection
的值為false,表示上下文和數據庫連接沒有關系,上下文釋放了,數據庫連接還沒釋放;反之為true的話,上下文釋放了,數據庫連接也就釋放了。
選擇合適的事務管理
目前,我們已經知道了好幾種使用EF處理事務的方法,下面一一對號入座:
- 如果只有一個DbContext類,那么應該盡力使用EF的默認事務管理。我們總應該將所有的操作組成一個在相同的DbContext對象的作用域中執行的工作單元,
SaveChanges()
方法會處理提交事務。 - 如果使用了多個DbContext對象,那么管理事務的最佳方法可能就是把調用放到
TransactionScope
對象的作用域中了。 - 如果要執行原生SQL,並想把這些操作和事務關聯起來,那么應該使用EF提供的
Database.BeginTransaction()
方法。然而這種方法只支持EF6,不支持之前的版本。 - 如果想為要求
SqlTransaction
的老項目使用EF,那么可以使用Database.UseTransaction()
方法,在EF6中可用。
本章小結
首先,我們看了下如何管理EF中並發相關的問題,然后討論了如何使用EF實現積極並發。我們也看了消極並發的一些基本概念以及為什么EF不支持和不推薦使用消極並發。
然后,我們看了如何使用EF管理事務。先是看了EF管理事務的默認實現,然后看了使用EF控制事務管理,最后看到了使用EF實現應用程序需要的大多數信息。
自我測試
- 在
EntityTypeConfiguration
類中,需要調用什么方法將一個屬性標記為並發屬性? - 哪一種異常類型表示並發錯誤?
如果您覺得這篇文章對您有價值或者有所收獲,請點擊右下方的店長推薦,然后查看答案,謝謝!
參考書籍:
《Mastering Entity Framework》
《Code-First Development with Entity Framework》
《Programming Entity Framework Code First》