【问题标题】:DbContext has been disposed and autofacDbContext 已被处理和 autofac
【发布时间】:2013-02-17 09:02:32
【问题描述】:

我有一个控制器:

private readonly ILogger _logger;    
private readonly IRepository _repository;

public HomeController(ILogger logger, IRepository repository)
{
   _logger = logger;
   _repository = repository;
}

这是存储库:

public class EfRepository : IRepository
{
    // ...methods for add, delete, update entities
    // ....

    public void Dispose()
    {
         if (this._context != null)
         {
             this._context.SaveChanges();
             (this._context as IDisposable).Dispose();
             this._context = null;
         }
    }
}

最后,IoC 中的注册类型:

_builder.RegisterType<Logger>().As<ILogger>();
_builder.RegisterType<EfRepository>().As<IRepository>().WithParameter("context", new PcpContext());

当我运行应用程序时出现此错误:

操作无法完成,因为 DbContext 已被 处置。

我尝试像这样更改注册 EfRepository:

_builder.RegisterType<EfRepository>()
   .As<IRepository>()
   .WithParameter("context", new PcpContext()).InstancePerLifetimeScope();

在这种情况下,第一个请求完成,但在尝试打开其他页面时,我再次收到错误消息。问题出在哪里?

【问题讨论】:

  • 永远不要在 dispose 时提交我们的 DbContext。发生异常时将调用 Dispose,但您不想在发生这种情况时保存任何更改。
  • @Steven:我删除了这一行,反正这并不能解决问题。
  • 每次我在 dispose 中看到 SaveChanges 时,编码器都会遇到问题。我还没有理解在 Dispose 中建议或推荐“提交”或“保存”的设计模式是什么。我建议您重新评估保存内部处置的计划。错误处理呢。为什么要将数据库的“提交”更改与垃圾收集相关联?值得一读msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspx 你什么时候触发 dispose?也许从这里开始:stackoverflow.com/questions/898828/…

标签: asp.net-mvc entity-framework entity-framework-5 autofac


【解决方案1】:

使用 WithParameter 方法时,每个已解析对象的参数实例都是相同的。因此,使用.WithParameter("context", new PcpContext()),您可以有效地将 PcpContext 类的相同实例用于任何已解析的 IRepository 实例。

使用您当前的代码,当一个 IRepository 实例被释放时,它也会释放该 PcpContext 实例。然后,任何后续尝试解析 IRepository 都将收到已释放的 PcpContext 实例。您需要一种方法来在请求结束时处理的每个 Http 请求上接收新的 EF DbContext 实例。

一种选择是为 IRepository 注册一个代码块,以便每次需要解析 IRepository 时执行代码块:

_builder.Register<IRepository>(c => new EfRepository(new PcpContext()))

更好的选择是创建一个新的IDatabaseContext 抽象,更新EfRepository,因此它依赖于新的IDatabaseContext 抽象而不是PcpContextclass(可能已经是这种情况:))。

IDatabaseContext 的实现类将是您的 PcpContext 类,它必须从 EF DbContext 继承,并且可能接收连接字符串作为参数。

public class EfRepository : IRepository
{
    private readonly IDatabaseContext _context;

    public EfRepository(IDatabaseContext context)
    {
        _context = context;
    }

    ...methods for add, delete, update entities

    //There is no longer need for this to be disposable.
    //The disaposable object is the database context, and Autofac will take care of it
    //public void Dispose()
}

public interface IDatabaseContext : IDisposable 
{
    ... declare methods for add, delete, update entities
}

public class PcpContext: DbContext, IDatabaseContext 
{
    public EntityFrameworkContext(string connectionString)
        : base(connectionString)
    {
    }

    ...methods exposing EF for add, delete, update entities

    //No need to implement IDisposable as we inherit from DbContext 
    //that already implements it and we don´t introduce new resources that should be disposed of
}

使用 IoC 容器并将生命周期管理的负担留给它们的想法会变得更好。现在您的 Repository 类不需要是一次性的,也不需要管理和处理其 IDatabaseContext 依赖项。 Autofac 将跟踪上下文实例并在适当的时候处理它。

出于同样的原因,您可能希望将 InstancePerLifetimeScope 与数据库上下文依赖项一起使用。这意味着同一个 Http 请求上的每个存储库实例共享同一个 EF 上下文,并在请求结束时释放。

_builder.RegisterType<EfRepository>()
   .As<IRepository>();

_builder.RegisterType<PcpContext>()
   .As<IDatabaseContext>()
   .WithParameter("connectionString", "NameOfConnStringInWebConfig")
   .InstancePerLifetimeScope();

【讨论】:

  • 我不使用 Autofac,所以读起来很有趣...+1 很好的分析和解释
  • 谢谢@soadyp!我使用 Unity 而不是 Autofac。虽然原理相同,但每个具体容器如何实现生命周期管理的细节会有所不同。 (例如,对于 Unity,我会为 IDatabaseContext 使用 Hierarchichal 生命周期,并结合每个 Http 请求创建的新子容器)
  • 如果我有多个有界 DbContext 怎么办?将 DbContext 注册为键控?
  • 您可以将所有 DbContexts 注册为命名注册,因此您可以配置您的存储库以接收适当的上下文
【解决方案2】:

我采用了@Daniel J.G 建议的“代码块”的简单解决方案(一个 lambda)。

下面是 Autofac 中的代码示例。 Daniels 的例子是 Unity,因为他也提到了自己。因为 OP 添加了 Autofac 作为标签,所以这似乎与我有关:

_builder.Register(c => new AppDbContext()).As(typeof(AppDbContext));

该代码解决了我在使用实体框架时遇到的DbContext has been disposed 问题。请注意,与大多数其他 DI 容器(包括 Unity)相比,Autofac 会切换已注册的事物及其解析的事物。

对于 OP 给出的代码示例,修复将是这样的:

_builder.Register(c => new EfRepository(new PcpContext())).As(IRepository);

请注意,最后一位是未经测试的代码。但是无论如何,您都应该参考 Daniels 的答案以获取更多信息,因为我认为他的“更好的选择”是正确的。但是如果你现在没有时间(比如我),你可以使用我的解决方案选项。只需添加一个 TODO,这样您就可以弥补您所招致的技术债务 :)。

当我这样做时,我会看看是否可以使用 Autofac 的工作代码更新这个答案,该代码遵循他的“更好的选择”。首先我要仔细阅读this article。在快速阅读时,在我看来 Autofac 人正在推广使用“服务定位器”来处理生命周期范围。但是根据 Mark Seemann 的说法是 an anti-pattern,所以我有一些东西要弄清楚……有没有 DI 专家有意见?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-27
    • 1970-01-01
    • 2017-03-19
    • 2017-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多