首页 > 代码库 > 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();
        }
}

修改完后部署重复上述步骤调试,预期中的结果。

下周再和团队一起系统地测试一遍,看看是不是有效果。

本来预期中的架构是没什么问题的,谁知当时又将操作类做成了单例模式而没有引起重视,为当前错误的爆发埋下了伏笔。架构还是应该多推敲,多考虑的。

要下班了,所以写的有些仓促;写得看起来很简单,但是研究的过程并不是特别简单,都是耗费时间的。特此记录。