項目中試用Entity Framework1.0作為ORM層,但發現確實是問題多多,今天主要說一下其交易處理部分。
對於一般的單表操作,比如2個add操作要在一個事務完成,可以使用隱含交易,如:
TestEntities t=new TestEntities();
t.AddToTableA(t1);
t.AddToTableB(t2);
t.SaveChanges();
兩句語句預設是在一個事務中提交的。
全是單表操作的話就按以上方式沒有問題,但如果要使用預存程序,那麼問題就來了。EF1.0對於預存程序支援並不好,凡是無傳回值和傳回值為標量的預存程序,使用Function Import之後,在cs檔案雷根本找不到對應的方法,要使用預存程序需要這麼寫:
TestEntities t=new TestEntities();
t.Connection.Open();
var tran=t.Connection.BeginTransaction();
EntityCommand cmd2 = new EntityCommand("TestEntities.UpdateForm", t.Connection as EntityConnection, tran as EntityTransaction);
cmd2.CommandType = CommandType.StoredProcedure;
cmd2.Parameters.Add(new EntityParameter(...));
cmd2.Parameters.Add(new EntityParameter(...));
cmd2.Parameters.Add(new EntityParameter(...));
cmd2.Parameters[0].Value = ...
cmd2.Parameters[1].Value =...
cmd2.Parameters[2].Value =...
cmd2.ExecuteNonQuery();
tran.Commit();
此處的UpdateForm為預存程序名稱,需要事先使用Function Import功能匯入實體模型才能使用。
同時,這裡的EntityTransaction事務可以和單表操作結合使用,比如以上代碼可以變為
cmd2.ExecuteNonQuery();
t.AddToTable1(t1);
t.SaveChanges();
tran.Commit();
那麼這個預存程序以及AddToTable1的操作是在一個事務內進行的,這是因為儘管SaveChanges()方法有自己的事務,但如果檢測到t已經開啟了現有的事務,那麼會沿用已有事務。
但是這樣寫有一個麻煩,entity command只支援查詢而不支援insert,update,delete,所以如果我要執行一句自訂語句update xxx set xxx where...就不行了。
EF Extension提供了一個擴充方法用於支援預存程序和自訂sql語句,用法如下:
var cmd = t.CreateStoreCommand("update form set xxx=xxx where formid=2", CommandType.Text);
或
var cmd = t.CreateStoreCommand("updateform", CommandType.StoredProcedure);
但是它使用的事務卻要求是SqlTransaction,用以下的寫法:
TestEntities t=new TestEntities();
t.Connection.Open();
EntityConnection con = t.Connection as EntityConnection;
var tran=con.StoreConnection.BeginTransaction();
var cmd = t.CreateStoreCommand("updateform", CommandType.StoredProcedure);
cmd.Transaction=tran;
cmd.ExecuteNonQuery();
tran.Commit();
注意這裡的StoreConnection,使用它建立的就是SqlTransaction。這樣的寫法可以確保在預存程序和自訂語句之間使用事務,但如果你想將一個預存程序和一個單表操作放在一起,則又會出問題。
cmd.ExecuteNonQuery();
t.AddToTable1(t1);
t.SaveChanges(); =》這裡會報錯!
tran.Commit();
也就是說SaveChanges()方法只認EntityTransaction,而CreateStoreCommand只認SqlTransaction,兩者無法統一。
所以如果要使用CreateStoreCommand方法,那麼事務內相關的所有操作都要封裝成預存程序形式,無法以單表操作進行調用;如果要使用EntityCommand+單表操作的事務,那麼就要放棄自訂Sql語句,或將他們都寫成預存程序的形式。
感謝韋恩卑鄙在評論中提供的解決方案:使用TransactionScope可以使之共存,示意代碼如下:
using (var dbtran = new TransactionScope(TransactionScopeOption.Required))
{
using (var t= new TestEntities())
{
using (t.Connection.CreateConnectionScope())// EF Extension中的擴充方法,作用是open connection並在dispose裡釋放它
{
t.AddToTableA(t1);
t.SaveChanges();
var cmd = t.CreateStoreCommand("update form set xxx=xxx where formid=2", CommandType.Text);
cmd.ExcuteNonQuery();
dbtran.Complete();
}
}
}
如果所有操作都是本地事務,那麼它將是一個輕型事務,不會用到MSDTC。如果是分布式的,則會自動升級為完全分散式交易,必須用到MSDTC。