【问题标题】:.NET Entity Framework and transactions.NET 实体框架和事务
【发布时间】:2011-03-17 00:41:52
【问题描述】:

作为 Entity Framework 的新手,我真的很纠结如何处理这组问题。在我目前正在进行的项目中,整个站点都与 EF 模型高度集成。起初,对 EF 上下文的访问是使用依赖注入引导程序控制的。出于操作原因,我们无法使用 DI 库。我删除了它,并在需要时使用了上下文对象的各个实例的模型。我开始收到以下异常:

类型“XXX”已被多次映射。

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

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

交易操作无法执行,因为有 处理此事务的待处理请求。

ExecuteReader 要求命令有一个事务,当 分配给命令的连接处于挂起的本地事务中。 该命令的 Transaction 属性尚未初始化。

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

这些异常充其量只是间歇性的,但我已设法使站点进入由于事务锁定而拒绝新连接的状态。不幸的是,我找不到异常详细信息。

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

老实说,我被困在这里,无法理解为什么(应该是)相当简单的操作会导致这样的问题......

【问题讨论】:

  • 你不能使用 IOC 引导程序太糟糕了,因为Ninject 的解决方案是将“通用”实例绑定到 请求范围,就像其他人一样建议:kernel.Bind<IRepository<SomeModel>>().To<EfGenericRepository<SomeModel, YourDbContext>>().InRequestScope(); -- 重要的部分是 InRequestScope

标签: .net sql-server-2005 entity-framework


【解决方案1】:

在 Web 应用程序中创建一个全局实体框架 DbContext 非常糟糕。 DbContext 类不是线程安全的(对于 Entity Framework v1 的 ObjectContext 类也是如此)。它是围绕unit of work 的概念构建的,这意味着您可以使用它来操作单个用例:因此用于业务交易。它旨在处理一个请求。

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

DbContext 包含数据库中实体的本地缓存。它允许您进行大量更改并最终将这些更改提交到数据库。当使用单个静态DbContext,多个用户在该对象上调用SaveChanges 时,它应该如何知道究竟应该提交什么,不应该提交什么?

因为它不知道,它会保存所有更改,但此时另一个请求可能仍在进行更改。如果幸运的话,EF 或您的数据库都会失败,因为实体处于无效状态。如果您不走运,处于无效状态的实体会成功保存到数据库中,您可能会在数周后发现您的数据已损坏。

您的问题的解决方案是创建at least one DbContext per request。虽然理论上你可以在用户会话中缓存一个对象上下文,但这也是一个坏主意,因为在这种情况下,DbContext 通常会活得太久,并且会包含陈旧的数据(因为它的内部缓存不会自动刷新) .

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

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

【讨论】:

  • 嗯,这确实是我认为我正在做的事情......我通常不会使用静态数据上下文对象,但我越来越绝望了。原来我有一个其他错误,我有单个上下文实例。然而,它们位于一个静态物体内,这似乎是原因。我生命中的 2 个小时没有回来......谢谢
  • 面对此类问题的最佳选择是什么?
  • 能否详细说明每个线程的 DbContext?它怎么不是线程安全的?
  • @Steven,所以根据您所说的,每个 Web 请求的单个上下文对象有意义吗?就像在控制器初始化中构造上下文对象一样。
  • @IsaacKleinman:请参阅this q/a 了解更多信息。
【解决方案2】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多