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

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

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

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

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

  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。

  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上下文创建的优化,那么它对数据库的交互呢?这是我们写了无数次的方法:

  1. public int AddUser(User user)
  2. {
  3. context.Add(user);
  4. return context.SaveChanges();
  5. }

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

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

  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,很赞。

EF上下文对象线程内唯一性与优化的更多相关文章

  1. EF上下文对象创建之线程内唯一

    在一次请求中,即一个线程内,若是用到EF数据上下文对象,就创建一个,那么会造成数据混乱,每次创建的对象执行相应的数据库操作,此同时,其他的EF对象内获得的数据可能已经是“过期”的了.即这个数据已经变动 ...

  2. 如何保证对象线程内唯一:数据槽(CallContext)

    CallContext 是类似于方法调用的线程本地存储区的专用集合对象,并提供对每个逻辑执行线程都唯一的数据槽.数据槽不在其他逻辑线程上的调用上下文之间共享.当 CallContext 沿执行代码路径 ...

  3. 【无私分享:ASP.NET CORE 项目实战(第二章)】添加EF上下文对象,添加接口、实现类以及无处不在的依赖注入(DI)

    目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 上一章,我们介绍了安装和新建控制器.视图,这一章我们来创建个数据模型,并且添加接口和实现类. 添加EF上下文对象 按照我们以前 ...

  4. ASP.net如何保证EF操作类线程内唯一

    说到线程内唯一,肯定会想到单例模式,但是如果多用户访问网站就会出现问题.ASP.net中有两种方法可以保证EF操作类线程内唯一(目前只会这两种,以后有好的方法再添加): 1.httpcontext(实 ...

  5. 添加EF上下文对象,添加接口、实现类以及无处不在的依赖注入(DI)

    添加EF上下文对象,添加接口.实现类以及无处不在的依赖注入(DI) 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 上一章,我们介绍了安装和新建控制器.视图,这一章我们来创建 ...

  6. C# 如何保证对象线程内唯一:数据槽(CallContext)

    如果说,一个对象保证全局唯一,大家肯定会想到一个经典的设计模式:单例模式,如果要使用的对象必须是线程内唯一的呢? 数据槽:CallContext,ok看下msdn对callcontent的解释. Ca ...

  7. C# 如何保证对象线程内唯一:数据槽(CallContext)【转载】

    如果说,一个对象保证全局唯一,大家肯定会想到一个经典的设计模式:单例模式,如果要使用的对象必须是线程内唯一的呢? 数据槽:CallContext,ok看下msdn对callcontent的解释. Ca ...

  8. MVC+Ef项目(3) 抽象数据库访问层的统一入口;EF上下文线程内唯一

    抽象一个数据库访问层的统一入口(类似于EF的上下文,拿到上下文,就可以拿到所有的表).实际这个入口就是一个类,类里面有所有的仓储对应的属性.这样,只要拿到这个类的实例,就可以点出所有的仓储,我们在 R ...

  9. EF 保证线程内唯一 上下文的创建

    1.ef添加完这个对象,就会自动返回这个对象数据库的内容,比如下面这个表是自增ID 最后打印出来的ID  就是自增的结果 2.lambda 中怎么select * var userInfoList = ...

随机推荐

  1. boost强分类器的实现

    boost.cpp文件下: bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator, int _numSampl ...

  2. UWP学习目录整理

    UWP学习目录整理 0x00 可以忽略的废话 10月6号靠着半听半猜和文字直播的补充看完了微软的秋季新品发布会,信仰充值成功,对UWP的开发十分感兴趣,打算后面找时间学习一下.谁想到学习的欲望越来越强 ...

  3. 6.DNS公司PC访问外网的设置 + 主DNS服务器和辅助DNS服务器的配置

    网站部署之~Windows Server | 本地部署 http://www.cnblogs.com/dunitian/p/4822808.html#iis DNS服务器部署不清楚的可以看上一篇:ht ...

  4. GitHub实战系列汇总篇

    基础: 1.GitHub实战系列~1.环境部署+创建第一个文件 2015-12-9 http://www.cnblogs.com/dunitian/p/5034624.html 2.GitHub实战系 ...

  5. 【.net 深呼吸】限制执行代码的权限

    前面好几篇文章,老周都跟大伙伴们聊了跟应用程序域有关的话题,干脆咱们一聊到底吧,做学问就应该这样,有恒心. App Domain的创建新应用程序域的方法中,有一个特殊的重载: public stati ...

  6. 如何解决流程开发中SheetRadioButtonList页面取值问题

    分享一个常见的取值问题. 应用场景: SheetRadioButtonList控件,点击其中一项执行事件操作.如果是页面加载的情况下,值就无法取到. 具体原因如下: 我给SheetRadioButto ...

  7. arcpy+PyQt+py2exe快速开发桌面端ArcGIS应用程序

    前段时间有一个项目,大体是要做一个GIS数据处理工具. 一般的方法是基于ArcObjects来进行开发,因为我对ArcObjects不太熟悉,所以就思考有没有其他简单快速的方法来做. 在查看ArcGI ...

  8. 图解Spark API

    初识spark,需要对其API有熟悉的了解才能方便开发上层应用.本文用图形的方式直观表达相关API的工作特点,并提供了解新的API接口使用的方法.例子代码全部使用python实现. 1. 数据源准备 ...

  9. useful commands for Kubernetes beginners

    Get pod ip and their coordinating NODE $ kubectl get pods -o wide If you want to get detailed inform ...

  10. 自定义Sublime Text的图标

    sublime text很赞,windows上最接近mac逼格的轻量编辑器,对于我这样比较喜欢格调的人来说,简直不二之选啊. 美中不足的是,看久了觉得它的图标似乎不是很上心.现在都流行扁平化了而它还停 ...