LINQ To SQL與Transaction

不管你是由我的書中,或是MSDN、網站處得知,LINQ to SQL之DataContext於SubmitChanges函式執行時,就算不指定Transaction,DataContext都會自動啟動一個Transaction,在許多ORM中,這算是相當常見的設計。 不過,如果我不想要這個預設的Transaction呢?

 

LINQ To SQLTransaction
 
/黃忠成
 
  不管你是由我的書中,或是MSDN、網站處得知,LINQ to SQL之DataContext於SubmitChanges函式執行時,就算不指定Transaction,DataContext都會自動啟動一個Transaction,在許多ORM中,這算是相當常見的設計。
 不過,如果我不想要這個預設的Transaction呢?原因有很多,可能是為了減少Lock的時間,或是效能、資源等等,反正就是不想要這個預設行為就是了!只要簡簡單單的更新資料就好了。
 可能嗎?就目前的LINQ To SQL設計來說,這個行為是強制性的,且無可調整之空間,但!若要強渡關山也不是沒交通工具,DataContext開了一個小口,讓我們可以覆載SubmitChanges函式,以此為起點,我利用了大量的Reflection技巧,讓Transaction消失。

 

using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Text;
using System.Reflection;
 
namespace ConsoleApplication35
{
    class Program
    {
        static void Main(string[] args)
        {
            NorthwindDataContext context = new NorthwindDataContext();           
            var item = (from s1 in context.Customers where s1.CustomerID == "VINET"
                        select s1).FirstOrDefault();
            if (item != null)
                item.ContactName = "VINET14";
            Console.ReadLine();
            context.DisableTransaction = true;
            context.SubmitChanges();
            Console.ReadLine();
        }
    }
 
    partial class NorthwindDataContext
    {
        public bool DisableTransaction { get; set; }
 
        private static MethodInfo _checkDispose = null;
        private static MethodInfo _checkNotInSubmitChanges = null;       
        private static MethodInfo _verifyTrackingEnabled = null;
        private static MethodInfo _acceptChanges = null;
        private static MethodInfo _submitChanges = null;
        private static FieldInfo _conflicts = null;
        private static FieldInfo _isInSubmitChanges = null;
        private static PropertyInfo _services = null;
        private static Type _changeProcessorType = null;
        private static ConstructorInfo _ci = null;
 
        static NorthwindDataContext()
        {
            _checkDispose = typeof(DataContext).GetMethod("CheckDispose",
                         BindingFlags.NonPublic | BindingFlags.Instance);
            _checkNotInSubmitChanges =
               typeof(DataContext).GetMethod("CheckNotInSubmitChanges",
                      BindingFlags.NonPublic | BindingFlags.Instance);
            _verifyTrackingEnabled = typeof(DataContext).GetMethod("VerifyTrackingEnabled",
                                      BindingFlags.NonPublic | BindingFlags.Instance);
            _acceptChanges = typeof(DataContext).GetMethod("AcceptChanges",
                             BindingFlags.NonPublic | BindingFlags.Instance);
            _conflicts = typeof(DataContext).GetField("conflicts",
                              BindingFlags.NonPublic | BindingFlags.Instance);
            _isInSubmitChanges = typeof(DataContext).GetField("isInSubmitChanges",
                                 BindingFlags.NonPublic | BindingFlags.Instance);
            _changeProcessorType = typeof(DataContext).Assembly.GetType(
                             "System.Data.Linq.ChangeProcessor");
            _services = typeof(DataContext).GetProperty("Services",
                        BindingFlags.NonPublic | BindingFlags.Instance);
            _ci = _changeProcessorType.GetConstructor(
                       BindingFlags.NonPublic | BindingFlags.Instance, null,
                      new Type[]                      
            { typeof(DataContext).Assembly.GetType("System.Data.Linq.CommonDataServices"),
              typeof(DataContext) }, null);
            _submitChanges = _changeProcessorType.GetMethod("SubmitChanges",
                     BindingFlags.NonPublic | BindingFlags.Instance);
        }
 
        public override void SubmitChanges(System.Data.Linq.ConflictMode failureMode)       
        {
            if (DisableTransaction)
            {
                _checkDispose.Invoke(this, null);
                _checkNotInSubmitChanges.Invoke(this, null);
                _verifyTrackingEnabled.Invoke(this, null);
                ((ChangeConflictCollection)_conflicts.GetValue(this)).Clear();
                try
                {
                    _isInSubmitChanges.SetValue(this, true);
                    object processor = _ci.Invoke(new object[]
                             { _services.GetValue(this, null), this });
                    _submitChanges.Invoke(processor, new object[] { failureMode });
                    _acceptChanges.Invoke(this, null);
                }
                finally
                {
                    _isInSubmitChanges.SetValue(this, false);
                }
            }
            else
                base.SubmitChanges(failureMode);
        }
    }
}
當DisableTransaction屬性為False時,SQL Profiler的畫面如下:
當設定DisableTransaction為True時,你會發現Transaction未被啟動。
處理完畢,我個人是覺得,LINQ To SQL應該把Transaction以Session概念處理,如Hibernate。