1. 程式人生 > >Entity Framework 資料併發訪問錯誤原因分析與系統架構優化

Entity Framework 資料併發訪問錯誤原因分析與系統架構優化

本文主要記錄近兩天針對專案發生的資料訪問問題的分析研究過程與系統架構優化,我喜歡說通俗的白話,高手輕拍

1. 發現問題

系統新模組上線後,使用頻率較高,故在實際使用和後期的問題重現測試中,產生了一下系列的資料訪問錯誤

錯誤是比較常見的錯誤

2. 分析問題

系統的架構為前端、業務層與資料層三層架構,採用Entity Framework 3.5作為資料處理技術,採用shared context per request模式,參照的是codeplex上的一個示例。示例地址(此文通俗易懂,程式碼結構也很清晰,個人很喜歡)

Entity模式的程式碼如下:

public partial class SPMIPEntities
    {
        public static SPMIPEntities Context
        {
            get
            {
                string objectContextKey = "MIP_" + HttpContext.Current.GetHashCode().ToString("x");
                if (!HttpContext.Current.Items.Contains(objectContextKey))
                {
                    HttpContext.Current.Items.Add(objectContextKey, new SPMIPEntities());
                }
                return HttpContext.Current.Items[objectContextKey] as SPMIPEntities;
            }
        }
    }

基於以上,根據系統的報錯位置研究Entity例項併發與共享的問題,搜尋了一些entity framework相關的資料。此問題主要從兩個角度去著手解決:縮短Entity例項的存在時間和降低Entity例項的共享性,並考慮效能,因為Entity需要手動Dispose。

首先新增手動Dispose邏輯,我們的專案為SharePoint應用程式,所以和一般的.net略有不同。自行開發了一個BasePage類,所有的應用程式頁面都繼承自該類,BasePage類又繼承自LayoutsPageBase類,要做到使用完Entity後及時Dispose,最好也寫在頁面的類似結束請求的事件裡,於是在後臺敲上protected空格override空格D->發現可重寫Dispose方法,大喜,程式碼如下:

public override void Dispose()
        {
            string objectContextKey = "MIP_" + HttpContext.Current.GetHashCode().ToString("x");
            if (HttpContext.Current.Items.Contains(objectContextKey))
            {
                SPMIPEntities ctx = HttpContext.Current.Items[objectContextKey] as SPMIPEntities;
                if (ctx != null)
                {
                    ctx.Dispose();
                    HttpContext.Current.Items.Remove(objectContextKey);
                }
            }
            base.Dispose();
        }

修改之後部署,測試,發現報錯了:

The ObjectContext instance has been disposed and can no longer be used for operations that require a connection

沒理由會這樣,因為每個請求都會例項化新的Entity,不可能會被提前釋放。

除錯程式碼,斷點卡到第一段程式碼get下面。重啟服務,訪問系統,附加程序開始除錯,第一次get進來了,new了一個Entity例項出去,頁面載入了出來;重新整理頁面,發現沒有再次中斷,到這裡,就非常的不合理了,肯定有忽視掉的地方,於是回過頭來一層一層檢視程式碼,發現業務層和資料層的類都被寫成了單例模式,舉例如下

public class DAC
    {

        #region 變數
        // 本類物件
        private static DAC _newNewInstance;
        private SPMIPEntities ctx = null;
        #endregion

        #region 建構函式

        /// <summary>
        /// 私有建構函式
        /// </summary>
        private DAC()
        {
            ctx = SPMIPEntities.Context;
        }

        #endregion

        #region 公有方法

        /// <summary>
        /// 本類例項物件
        /// </summary>
        /// <returns>
        /// 返回DAC物件
        /// </returns>
        public static DAC GetNewInstance()
        {
            if (_newNewInstance == null)
            {
                _newNewInstance = new DAC();
            }
            return _newNewInstance;
        }
}

問題一定就在這裡了,將單例模式改掉,修改為如下結構

public class DAC
    {
        private SPMIPEntities ctx = null;

        private DAC()
        {
            ctx = SPMIPEntities.Context;
        }

        public static DAC GetNewInstance()
        {
            return new DAC();
        }
}

修改完後部署重複上述步驟除錯,預期中的結果。

下週再和團隊一起系統地測試一遍,看看是不是有效果。

本來預期中的架構是沒什麼問題的,誰知當時又將操作類做成了單例模式而沒有引起重視,為當前錯誤的爆發埋下了伏筆。架構還是應該多推敲,多考慮的。

要下班了,所以寫的有些倉促;寫得看起來很簡單,但是研究的過程並不是特別簡單,都是耗費時間的。特此記錄。