.NETentity framework和事务

作为新的entity framework,我真的很困扰如何处理这一系列问题。 在我目前正在进行的项目中,整个网站与EF模型高度集成。 起初,使用dependency injection引导程序来控制对EF上下文的访问。 出于操作原因,我们无法使用DI库。 我删除了这个,并在必要时使用了上下文对象的单个实例的模型。 我开始得到以下例外:

types“XXX”已被映射多次。

我们得出的结论是,不同的背景是造成这个问题。 然后,我将上下文对象抽象成一个单独的静态实例,每个线程/页面都访问它。 我现在得到几个有关交易的例外之一:

新的事务是不允许的,因为会话中还有其他线程正在运行。

事务操作无法执行,因为在此事务上有未决的请求。

当分配给命令的连接处于未决的本地事务中时,ExecuteReader需要该命令进行事务。 该命令的Transaction属性尚未初始化。

最后一个例外发生在加载操作上。 我没有试图将上下文状态保存回失败的线程上的Db。 还有另外一个线程正在执行这样的操作。

这些例外情况最好是断断续续的,但是我设法让网站进入一个由于交易locking而拒绝新连接的状态。 不幸的是我找不到例外的细节。

我想我的第一个问题是,EF模型应该从一个静态单个实例使用? 另外,是否可以删除EF中的交易? 我试过使用一个TransactionScope对象没有成功…

说实话,我在这里呆了很多,并不明白为什么(应该是)相当简单的操作导致这样的问题…

在Web应用程序中创build一个全局ObjectContext是非常糟糕的。 ObjectContext类不是线程安全的。 它是围绕着工作单元的概念而build立的,这意味着您可以使用它来运行单个用例:从而进行商业交易。 它意味着处理一个单一的请求。

发生exception是因为每个请求都创build一个新的事务,但是尝试使用相同的ObjectContext 。 你很幸运, ObjectContext检测到这个并引发exception,因为现在你发现这是行不通的。

请想想为什么这不能工作。 ObjectContext包含数据库中的实体的本地caching。 它允许你进行一些更改,最后将这些更改提交给数据库。 如果使用单个静态ObjectContext ,并且有多个用户在该对象上调用SaveChanges ,那么应该如何知道究竟应该提交什么,什么不应该提交? 因为它不知道,它会保存所有的变化,但在那个时候,另一个用户可能仍然在进行更改。 幸运的是,EF或数据库将失败,因为实体处于无效状态。 如果你不幸的是处于无效状态的对象被成功保存到数据库中,你可能会在几周后发现你的数据库充满了垃圾。 解决您的问题是每个请求至less创build一个ObjectContext 。 虽然理论上你可以在用户会话中caching一个对象上下文,但这也是一个坏主意,因为ObjectContext通常会活太长,并且会包含陈旧的数据(因为它的内部caching不会自动刷新)。

更新

另外请注意,每个线程有一个ObjectContext和完整的Web应用程序只有一个单一实例一样糟糕。 ASP.NET使用线程池,这意味着在Web应用程序的生命周期中将创build有限数量的线程。 这基本上意味着那些ObjectContext实例在这种情况下仍然会在应用程序的整个生命周期中生存,从而导致数据过时的问题。

你可能会认为每个线程有一个DbContext实际上是线程安全的,但通常情况并非如此,因为ASP.NET有一个asynchronous模型,允许在不同的线程上完成请求,而不是在开始的时候(和最新版本的MVC和Web API甚至允许任意数量的线程按顺序处理单个请求)。 这意味着启动一个请求并创buildObjectContext的线程可以在初始请求完成之前处理另一个请求。 然而,该请求中使用的对象(例如网页,控制器或任何业务类)仍可能引用该ObjectContext 。 由于新的Web请求在同一个线程中运行,它将获得与旧请求所使用的ObjectContext实例相同的ObjectContext实例。 这又会在您的应用程序中导致竞争条件,并导致与全局ObjectContext实例相同的线程安全问题。

当你提到你的“网站”的问题,我认为这是一个Web应用程序。 对于整个应用程序来说,静态成员只存在一次,如果你在整个应用程序中使用一个单一的上下文实例的单例types模式,那么所有types的请求都将在整个应用程序中处于各种状态。

单个静态上下文实例将不起作用,但是每个线程的多个上下文实例会很麻烦,而且不能混合和匹配上下文。 你需要的是每个线程的单个上下文。 我们已经在我们的应用程序中使用dependency injectiontypes模式完成了这个任务 我们的BLL和DAL类在方法中使用上下文作为参数,这样你可以做如下的事情:

 using (TransactionScope ts = new TransactionScope()) { using (ObjectContext oContext = new ObjectContext("MyConnection")) { oBLLClass.Update(oEntity, oContext); } } 

如果您需要在更新中调用其他BLL / DAL方法(或者您select的任何方法),则只需传递相同的上下文。 这种方式更新/插入/删除是primefaces,在一个单一的方法eveything使用相同的上下文实例,但该实例没有被其他线程使用。