這個文章寫的有點滯后了,呵呵,因為總想把之前不確定的東西確定了之后,再寫這篇,之前的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架構的核心代碼,主要體現在生成數據上下文對象上,及如何去避免並發沖突的產生,而對於並發沖突我們會在另一篇文章中做詳細的說明。敬請期待!