EF中DataContext以及对应实体的生命周期
Enitity Framework的文章非常多,而且使用起来也非常简单。当然一旦遇到一个异常时,就非常让人头疼。最近一直用EF,遇到一些问题,所以分享出来对大家或许有些帮助。在这里不会研究源码,只说原理,并分享一些最佳实践的代码。有说的不对的地方,请过路人指正。
DataContext对象应该生成多少个?
当我们new一个新的DataContext对象并从数据库获取相关实体时,意味着DataContext会对这些获取到的实体进行状态跟踪。本质上讲,你只得到数据库某个数据的一个拷贝。当你对实体进行增加删除时,实体本身并没有进行增删改,但DataContext会把这些状态都记录下来;一旦调用dbContext.SaveChanges(),DataContext才会生成对应sql命令,把这些更改保存到数据库中。如果你以为这是理所当然的事情,那么请看下面一个例子:
User user = null; using(MyDataContext context = new MyDataContext()) { user = context.Users.First(); } user.Age = 18; using(MyDataContext context2 = new MyDataContext()) { context2.Entry<User>(user).State = EntityState.Modified; context2.SaveChanges(); }
这里会抛出一个异常。异常是:An entity object cannot be referenced by multiple instances of IEntityChangeTracker。IEntityChangeTracker相当于实体的跟踪器。一个DataContext管理了另一个DataContext下的实体,这是不允许的。打个比方说,DataContext就像一个国王,负责他的子民的生老病死,另一个国家的国王是不能对本国的子民进行审判的,这是越界了,要引发战争的。【可以通过Attach和DeAttach来修改国籍,达到被另一个DataContext管理的目的】这么看来,在一个应用里似乎只需要一个国王就能搞定所有事情,事实是不是这样的呢?
上面说过,每个通过同一个DataContext得到的实体都会被它跟踪管理,那么如果多个客户端都使用一个DataContext获取实体,而又都在各自的客户端更改,当同时提交SaveChanges时,哦,麻烦出现了。永远不要出现单例的DataContext用在应用程序环境里,那会导致各种错误,最基本的是线程不安全,你对国王的各种奏章导致他分身乏术,出现并发冲突。
那么在一个应用里,要有多少个国王DataContext才行呢?
根据各种经验显示,对Asp.Net网站,应该是每一个请求一个DataContext,对于Winform以及WPF应该是每个窗体或表现一个DataContext,对于WebService应该是每个调用一个DataContext;
对于Asp.Net网站,因为是一个请求一个DataContext,所以我们可以保存到HttpRequst的缓存中,保证了DataContext的唯一性。最佳实践代码如下:
public static MyDataContext Instance { get { if (HttpContext.Current != null && HttpContext.Current.Cache["MyDataContext "] == null) { HttpContext.Current.Cache["MyDataContext "] = new MyDataContext (); } return HttpContext.Current.Cache["MyDataContext "] as MyDataContext ; } set { if(HttpContext.Current != null) HttpContext.Current.Cache["MyDataContext "] = value; } }
对于winform或WPF,可以使用如下代码:
[ThreadStatic] private static MyDataContext instance; public static MyDataContext Instance(){ if (context == null) instance= new MyDataContext (); return instance; }
使用ThreadStatic保证线程安全,为什么在asp.net下不使用这个属性来确保安全呢?是因为IIS下有重用线程的可能性,对于另一个请求,IIS会继续使用这个DataContext进行操作,那么,你看,又会并发冲突了。