关于sql server 2005:.NET实体框架和事务

.NET Entity Framework and transactions

作为实体框架的新手,我真的很难继续处理这组问题。在我目前正在进行的项目中,整个站点与EF模型紧密集成。首先,使用依赖注入引导程序控制对EF上下文的访问。由于操作原因,我们无法使用DI库。我删除了这个,并在需要时使用了上下文对象的单个实例的模型。我开始出现以下异常:

The type 'XXX' has been mapped more than once.

我们得出的结论是上下文的不同实例导致了这个问题。然后,我将上下文对象抽象为一个单独的静态实例,每个线程/页面都可以访问该实例。我现在得到了几个关于交易的例外情况之一:

New transaction is not allowed because there are other threads running
in the session.

The transaction operation cannot be performed because there are
pending requests working on this transaction.

ExecuteReader requires the command to have a transaction when the
connection assigned to the command is in a pending local transaction.
The Transaction property of the command has not been initialized.

最后一个异常发生在加载操作上。我没有试图将上下文状态保存回失败线程上的数据库。然而,有另一个线程执行这样的操作。

这些异常最多是间歇性的,但我已经设法使站点进入一种状态,在这种状态下,由于事务锁,新的连接被拒绝。不幸的是,我找不到异常详细信息。

我想我的第一个问题是,应该从静态的单个实例使用EF模型吗?此外,是否可以消除对EF中事务的需求?我试过使用一个TransactionScope对象,但没有成功…

老实说,我在这里停留了很久,不明白为什么(应该是什么)相当简单的操作会导致这样的问题…


在Web应用程序中创建全局ObjectContext非常糟糕。ObjectContext类不是线程安全的。它是围绕工作单元的概念构建的,这意味着您使用它来操作单个用例:因此对于业务事务来说。它是为了处理一个单一的请求。

发生异常是因为您为每个请求创建了一个新事务,但尝试使用相同的ObjectContext。你很幸运,ObjectContext检测到了这一点并抛出了一个异常,因为现在你发现这不起作用了。

请考虑一下为什么这不起作用。ObjectContext包含数据库中实体的本地缓存。它允许您进行大量更改,并最终将这些更改提交到数据库。当使用单个静态ObjectContext时,当多个用户在该对象上调用SaveChanges时,应该如何知道应该提交什么,不应该提交什么?因为它不知道,所以它将保存所有更改,但此时另一个用户可能仍在进行更改。如果幸运的话,EF或数据库都将失败,因为实体处于无效状态。如果你不走运,处于无效状态的对象会成功保存到数据库中,几周后你可能会发现数据库中充满了垃圾。您的问题的解决方案是每个请求至少创建一个ObjectContext。虽然理论上可以在用户会话中缓存对象上下文,但这也是一个坏主意,因为ObjectContext通常寿命太长,并且包含过时的数据(因为其内部缓存不会自动刷新)。

更新:

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

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


当你提到你问题中的"站点"时,我假设这是一个Web应用程序。对于整个应用程序,静态成员只存在一次,如果在整个应用程序中使用具有单个上下文实例的单例类型模式,则所有类型的请求都将在整个应用程序中处于各种状态。

一个静态上下文实例不能工作,但是每个线程有多个上下文实例会很麻烦,而且您不能混合和匹配上下文。您需要的是每个线程一个上下文。我们已经在应用程序中使用依赖注入类型模式完成了这项工作。我们的BLL和DAL类将上下文作为方法中的参数,这样您就可以执行如下操作:

1
2
3
4
5
6
7
using (TransactionScope ts = new TransactionScope())
{
    using (ObjectContext oContext = new ObjectContext("MyConnection"))
    {
        oBLLClass.Update(oEntity, oContext);
    }
}

如果需要在更新中调用其他BLL/DAL方法(或您选择的任何方法),只需传递相同的上下文即可。这样,更新/插入/删除是原子的,在一个方法中的所有操作都使用相同的上下文实例,但其他线程没有使用该实例。