• EF上下文对象线程内唯一性与优化


      在一次请求中,即一个线程内,若是用到EF数据上下文对象,就创建一个,这也加是很多人的代码中习惯在使用上下文对象时,习惯将对象建立在using中,也是为了尽早释放上下文对象, 但是如果有一个业务逻辑调用了多个dal层的方法,交互数据库多次,这样效率会低一些,而且在使用EF的情况下,我们通常把SaveChange这个方法提到业务逻辑层(下文中会提到),不保证同一个业务逻辑使用的是同一个上下文对象,事务,工作单元模式将无法实现。而且可能造成数据混乱,每次创建的对象执行相应的数据库操作,与此同时,同一次的请求可能包含对数据的不同操作。其他的EF对象内获得的数据可能已经是“过期”的了。即这个数据已经变动过。这就是脏读。

            为了解决这个问题,关键就是上下文对象的创建问题。

            这里首先想到单例模式,不过在这里,不适合用,原因是使用单例模式,会使EF对象得不到及时的资源释放。想象一下,无数个请求对数据库的访问,DbContext对象容器无数次增加对Model对象的Attach监控,内存就爆了。

            优化就是折中的过程,所以第二种方式考虑保证在线程内对象唯一,对于每一个请求使用同一个上下文。如何保证呢,通过微软ASP机制线程相关的HttpContext对象以及CallContext对象。前面一篇文章中说过,HttpContext机制其实就是依靠CallContext对象实现的。先来看使用CallContext解决这个问题

    你可以这样做,在网站Common中添加处理类:

    [csharp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /// <summary>  
    2.    /// 用来创建EF上下文对象,且保证线程内唯一。  
    3.    /// </summary>  
    4.    public class DbContextFactory  
    5.    {  
    6.        //DbContext在System.Data.Entity;中,不过这里直接只引用这一个不行,还有EF其他的一些NameSpace所以直接添加一个实体模型,所有引用都进来了,然后再把模型删了  
    7.        public static DbContext GetDbContext()  
    8.        {  
    9.            DbContext dbContext = (DbContext)CallContext.GetData("dbContext");  
    10.            if (dbContext == null)  
    11.            {  
    12.                dbContext = new WebEntities();  
    13.                CallContext.SetData("dbContext", dbContext);  
    14.            }  
    15.            return dbContext;  
    16.        }  
    17.    }  

               是不是很像缓存的使用策略。

               仔细思考一阵后发现,上面使用CallContext来存储有什么问题?就是说上面是把上下文对象依赖于一个线程。那么由于线程池的存在,线程在处理完一个请求之后,并没有被销毁,存储在CallContext中的上下文对象也一直存在,如果是下一次拿出这个线程去处理另一个请求,这个上下文对象其实也在不断的膨胀,只不过比全局的膨胀的稍微慢一些。而且,有时候一个线程并不一定是拿去处理请求了,如果是服务器拿去处理其他的业务,那就可能引发一些其他的问题。

            所以,改进一下上面的办法,借鉴一下J2EE的hibernate和mybatis,在DbContextFactory中添加一个remove方法,在业务逻辑层中每次请求使用完上下文之后,就把它从线程中移除。

            解决了,可是这办法实在是。。。那如果我一次请求要调几次业务逻辑呢,还是要创建多次上下文。而且这样手动管理的方式,让人痛苦。相信也是由于这个原因,在spring+hibernate中大家也是更愿意用HibernateTemplate而不是HibernateDaoSupport。

            其实我们还有更好的办法,在HttpContext中有一个Items属性,它也可以用来保存key-value,这就完美了,一次请求正好对应着一个HttpContext,请求结束,它自动释放,EF上下文也就不存在了。把上面代码中的CallContext改为HttpContext.Current.Items,OK。

    [csharp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. public static DbContext DbContext()  
    2.         {  
    3.             DbContext dbContext = HttpContext.Current.Items["dbContext"] as DbContext;  
    4.             if (dbContext == null)  
    5.             {  
    6.                 dbContext = new WebEntities();  
    7.                 HttpContext.Current.Items["dbContext"] =  dbContext;  
    8.             }  
    9.             return dbContext;  
    10.         }  

              再说SavaChanges这个方法,我们现在可以做到EF上下文创建的优化,那么它对数据库的交互呢?这是我们写了无数次的方法:

    [csharp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. public int AddUser(User user)  
    2.         {  
    3.             context.Add(user);  
    4.             return context.SaveChanges();  
    5.         }  


              当我们使用一个业务逻辑复杂的方法中,它可能需要使用到多个dal层对象或者说调用多次dal层的方法,上面的写法,调几次,EF就与数据库交互了几次,效率还是很低。那我们何不把与数据库的交互方法SaveChanges()提到bll层来调用,由bll层方法来调用,一次的业务逻辑,只交互一次,形成一种工作单元模式。

              那么怎么提取,由于我们上下文对象在请求内唯一,那么就再简单不过了。

    [csharp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. public class DbSession  
    2. {  
    3.     public static int SaveChanges()  
    4.     {  
    5.         return DbContextFactory.GetDbContext().SaveChanges();  
    6.     }  
    7. }  

              为什么把这个类名取为DbSession,学习JavaEE的朋友可能马上想到了MyBatis,Hibernate,我们封装了一个对数据库的单元操作,与数据库进行交互,就是一次与数据库的会话。

            另外,我刚接触EF的时候就有这个疑问,EF如果做到事务的处理,用TransactionScope或DbConnection?大可不必,如果我们把SaveChanges()提到业务逻辑层,就组成了一个事务单元,再联想一下spring,为什么会把声明式事务放在Service层而不是Dao层,而且SaveChanges()这个方法其实本身事务的特性,如果保持了上下文对象的唯一性,间接也是完成了事务单元。

    ===========================2016-11-7===========================

            最近在MVC里面用了一下NHibernate,仍然需要像管理EF上下文一样管理Session对象,同样,我们也可以把它"缓存"在HttpContext中,但是NHibernate已经帮我们完成了类似的工作。详情参见http://www.cnblogs.com/13yan/archive/2013/05/17/3083552.html。作者还给大家提供了一个NHibernateHelper,很赞。

  • 相关阅读:
    bat 批处理编写
    dos 命令
    反射
    反爬机制和破解方法汇总
    pandas
    谷歌历史浏览器下载
    python-----pip安装源选择(亲测有效)
    deepin 20.1 系统未安装pip
    python自带库-----os.path
    python 自带库---os库
  • 原文地址:https://www.cnblogs.com/xuanqust/p/6244090.html
Copyright © 2020-2023  润新知