被問到在EF環境要如何控制將某些DB操作包含在Transaction範圍內、將某些排除在外? 整理成簡單範例方便說明。
範例程式碼共有三段DB操作,第一段是寫入追蹤資訊到ActLog資料表、第二、三段則是各寫入一筆Player資料,為了模擬交易Rollback情境,故意讓兩筆Player的Primary Key相同。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
namespace TestNoTran
{
class Program
{
staticvoid Main(string[] args)
{
using (TransactionScope tx = new TransactionScope())
{
try
{ //...其他程式邏輯(省略)...
SomeLogHelper.Log("Debug: Insert To DB");
SomeDALHelper.InsertPlayer("A1", "U1", DateTime.Today, 100);
//故意寫入PK相同的第二筆資料,將引發錯誤
SomeDALHelper.InsertPlayer("A1", "U2", DateTime.Today, 120);
tx.Complete();
}
catch (Exception ex)
{
Console.WriteLine("Error: {0}", ex.Message);
}
Console.Read();
}
}
}
class SomeDALHelper
{
publicstaticvoid InsertPlayer(
string id, string name, DateTime regDate, int score)
{
using (var ctx = new LabEntities())
{
var p1 = new Player()
{
UserId = id,
UserName = name,
RegDate = regDate,
Score = score
};
ctx.Players.Add(p1);
ctx.SaveChanges();
}
}
}
class SomeLogHelper
{
publicstaticvoid Log(string msg)
{
using (var ctx = new LabEntities())
{
ctx.ActLogs.Add(new ActLog()
{
Info = msg
});
ctx.SaveChanges();
}
}
}
}
執行程式,一如預期,會得到因PK重複引發的錯誤:
Error: System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.UpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Violation of PRIMARY KEY constraint 'PK_Player'. Cannot insert duplicate key in object 'dbo.Player'. The duplicate key value is (A1).
檢查資料庫,ActLog及Player資料表均空空如也,代表三個DB動作一起Rollback了。如果我們希望寫ActLog的部分不要參與交易,無論如何都將資訊寫入資料庫,該怎麼做?
最簡單的做法是用另一個TransactionScope將寫ActLog部分包起來,並指定初始化參數為TransactionScopeOption.Suppress,宣告在此範圍內的DB動作不需要參與交易。如下:
staticvoid Main(string[] args)
{
using (TransactionScope tx = new TransactionScope())
{
try
{ //...其他程式邏輯(省略)...
//宣告出一段不參與交易的範圍
using (TransactionScope tx2 =
new TransactionScope(TransactionScopeOption.Suppress))
{
SomeLogHelper.Log("Debug: Insert To DB");
}
SomeDALHelper.InsertPlayer("A1", "U1", DateTime.Today, 100);
//故意寫入PK相同的第二筆資料,將引發錯誤
SomeDALHelper.InsertPlayer("A1", "U2", DateTime.Today, 120);
tx.Complete();
}
catch (Exception ex)
{
Console.WriteLine("Error: {0}", ex.ToString());
}
Console.Read();
}
}
重新執行程式,錯誤依舊,Player資料表仍無資料,但ActLog資料表會留下一筆記錄,實現了排除在交易範圍外的目標。
最後補充,除了使用TransactionScope外,還有其他處理EF交易的方式,如: 讓DbContext.SaveChanges()自動將一連串動作包成交易、讓多個DbContext共用連線並控制該連線交易狀態等,細節可參考舊文,該文談的雖是LINQ to SQL,但與EF運作原理大同小異。