LINQ-to-SQL那點事~LINQ-to-SQL中的資料快取與應對

張佔嶺發表於2013-06-19

回到目錄

這個文章寫的有點滯後了,呵呵,因為總想把之前不確定的東西確定了之後,再寫這篇,之前的LINQ-to-SQL那點事,請點這裡

LINQ-to-SQL中的資料快取與應對

Linq-to-SQL它是微軟自己推出的一個輕量級的ORM框架,它很好地完成了與SQLSERVER資料庫的對映(它目前只支援SQLSERVER,也不會有以後的,因為微軟不對它進行更新了),在使用它時,微軟提出了“資料上下文”的概念,這個上下文(context)類似於HttpContext,RequestContext,是指對某種事物的完整的抽象,把對這種事物的操作都整合在上下文中。

Linq-to-SQL的上下文被稱為DataContext,它進一步的封裝了SQL語句,亮點在於它的查詢上,支援延時查詢,再配合linq的語法,使得開發人員在寫程式碼時很優雅,程式碼表現力更強。

DataContext在效能方面提出了快取的概念,它可以裝查詢出來的資料快取到上下文中(這有時會產生併發問題),對於Insert,Update,Delete這類執行類操作也提供了快取語句,每當SubmitChange方法被觸發時,這時快取的語句被一次性的提交到SQLSERVER,之後將當前上下文的快取語句清空。

一個執行緒單例的資料上下文的提出:

當你希望把延時的資料返回到表示層時,DataContext如果被dispose之後,這種操作是不被允許的,這是正確的,因為你的資料上下文可能在表示層方法執行前已經被dispose了,一般這種程式碼會被這樣書寫:

    public IQueryable<Order_Info> GetOrder_Info(Expression<Func<Order_Info, bool>> predicate)
        {
            using (var datacontext = new LinqDataContext())
            {
                return datacontext.Where(predicate);
            }
        }

這段程式碼在執行上當然是有問題的,使用了using關鍵字後,在方法return這資料上下文DataContext將會被dispose,這是正常的,而由於linq語句返回的是IQueryable延時結果集,它將不會立即執行,只有真正返回資料時才會通過DataContext與SQLSERVER進行互動,而在上層方法中,由於DataContext這時已經被dispose了,所以,語句最終會報異常。

面對這種問題,我們知道了它的原因,所以接下來就尋找一種解決方法,即不叫DataContext立即dispose的方法,你可能會很容易的想到“把using去掉不就可以了”,事實上,如果你對linq to sql瞭解的話,這種做法是不可取的,因為這樣,你在業務邏輯層無法實現“複雜查詢,linq join”(一般地,我們為每個DAL層的表物件寫幾個方法,可能是根據條件去查詢資料的方法),為什麼呢?因為,對於一個linq查詢語句來說,你的資料上下文必須是同一個才行,如果使用者業務使用一個上下文,而訂單業務使用另一個上下文,那麼,這兩個業務進行組成查詢時,就會出現不同資料上下文的問題。

程式碼可能是這樣:

   var linq =from user in userBLL().GetModel()
             join order in orderBLL().GetModel() on user.UserID equals order.UserID
              select new user_Ext
{ ... }

為資料上下文新增一個工廠,用來生成由UI執行緒產生的資料上下文,再把這些上下文放在一個由UI執行緒作為鍵的字典裡,當UI執行緒中的資料上下文在進行SubmitChange出現異常進,我們再將當然上下文dispose,並從資料上下文字典中移除它。

資料上下文工廠及資料上下文基類程式碼如下:

    /// <summary>
    /// 資料庫建立工廠
    /// Created By : 張佔嶺
    /// Created Date:2011-10-14
    /// Modify By:
    /// Modify Date:
    /// Modify Reason:
    /// </summary>
    internal static class DbFactory
    {
        #region Fields
        static readonly string strConn = System.Configuration.ConfigurationManager.ConnectionStrings["test"].ToString();
        static System.Timers.Timer sysTimer;
        volatile static Dictionary<Thread, DataContext[]> divDataContext;
        #endregion

        #region Constructors
        static DbFactory()
        {
            divDataContext = new Dictionary<Thread, DataContext[]>();
            sysTimer = new System.Timers.Timer(10000);
            sysTimer.AutoReset = true;
            sysTimer.Enabled = true;
            sysTimer.Elapsed += new System.Timers.ElapsedEventHandler(sysTimer_Elapsed);
            sysTimer.Start();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// 清理DbContext上下文
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void sysTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            List<Thread> list = divDataContext.Keys
                                              .Where(item => item.ThreadState == ThreadState.Stopped)
                                              .ToList();
            if (list != null && list.Count > 0)
            {
                foreach (var thread in list)
                {
                    foreach (var context in divDataContext[thread])
                    {
                        if (context != null)
                            context.Dispose();
                    }
                }
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// 通過工廠的製造模式獲取相應的LINQ資料庫連線物件
        /// </summary>
        /// <param name="dbName">資料庫名稱(需要與真實資料庫名稱保持一致)</param>
        /// <returns>LINQ資料庫連線物件</returns>
        public static DataContext Intance(string dbName)
        {
            return Intance(dbName, Thread.CurrentThread);
        }

        /// <summary>
        /// 通過工廠的製造模式獲取相應的LINQ資料庫連線物件
        /// </summary>
        /// <param name="dbName">資料庫名稱(需要與真實資料庫名稱保持一致)</param>
        /// <param name="thread">當前執行緒引用的物件</param>
        /// <returns>LINQ資料庫連線物件</returns>
        public static DataContext Intance(string dbName, Thread thread)
        {

            if (!divDataContext.Keys.Contains(thread))
            {
                divDataContext.Add(thread, new DataContext[3]);
            }

            if (dbName.Equals("test"))
            {
                if (divDataContext[thread][0] == null)
                {
                    divDataContext[thread][0] = new DAL.dbDataContext(strConn);
                }
                return divDataContext[thread][0];
            }

return null;

        }

        /// <summary>
        /// 手動清除資料上下文,根據執行緒
        /// </summary>
        /// <param name="thread"></param>
        public static void ClearContextByThread(Thread thread, DataContext db)
        {
            divDataContext.Remove(thread);//從執行緒字典中移除
            db.Dispose();//釋放資料資源
        }
        #endregion

    }

 

下面是DataContext基類,已經對SubmitChanges(SaveChanges)方法進行了優化,手動dispose上下文。

/// <summary>
    /// Repository基類
    /// 所有linqTosql上下文物件都繼承它
    /// </summary>
    public abstract class ContextBase
    {
        protected DataContext _db { get; private set; }
        protected IUnitOfWork UnitOfWork { get; private set; }
        public ContextBase(DataContext db)
        {
            _db = db;
            UnitOfWork = (IUnitOfWork)db;
        }
        public void SaveChanges()
        {
            ChangeSet cSet = _db.GetChangeSet();
            if ((cSet.Inserts.Count > 0
                || cSet.Updates.Count > 0
                || cSet.Deletes.Count > 0)
                && !UnitOfWork.IsNotSubmit)
            {
                try
                {
                    UnitOfWork.SaveChanges();
                }
                catch (System.Data.Linq.ChangeConflictException)
                {
                    foreach (System.Data.Linq.ObjectChangeConflict occ in _db.ChangeConflicts)
                    {
                        // 使用當前資料庫中的值,覆蓋Linq快取中實體物件的值  
                        occ.Resolve(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
                        // 使用Linq快取中實體物件的值,覆蓋當前資料庫中的值  
                        occ.Resolve(System.Data.Linq.RefreshMode.KeepCurrentValues);
                        // 只更新實體物件中改變的欄位的值,其他的保留不變  
                        occ.Resolve(System.Data.Linq.RefreshMode.KeepChanges);
                    }
                    UnitOfWork.SaveChanges();
                }
              catch (Exception)//如果出現異常,就從資料字典中清除這個鍵值對
                {
                    DbFactory.ClearContextByThread(System.Threading.Thread.CurrentThread, _db);
              }
            }
        }
    }

下面是一個領域的repository基類,程式碼如下:

    /// <summary>
    /// Test資料庫基類
    /// Created By : 張佔嶺
    /// Created Date:2011-10-14
    /// Modify By:
    /// Modify Date:
    /// Modify Reason:
    /// </summary>
    public abstract class TestBase : ContextBase
    {
        #region Constructors
        public EEE114Base()
            : this(null)
        { }

        public EEE114Base(IUnitOfWork db)
            : base((DataContext)db ?? DbFactory.Intance("test", Thread.CurrentThread))
        { }
        #endregion

        #region Protected Properies
        /// <summary>
        /// 可以使用的資料庫連線物件
        /// [xxb]
        /// </summary>
        protected dbDataContext db
        {
            get
            {
                return (dbDataContext)base._db;
            }
        }

        #endregion
 }
}

OK,這就是改善之後的linq to sql架構的核心程式碼,主要體現在生成資料上下文物件上,及如何去避免併發衝突的產生,而對於併發衝突我們會在另一篇文章中做詳細的說明。敬請期待!

回到目錄

相關文章