13

我有一个控制器:

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();

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

4

2 回答 2

19

使用 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 抽象而不是PcpContext类(可能已经是这种情况:))。

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();
于 2013-02-17T11:27:43.150 回答
0

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

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

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

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

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

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

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

When I do I'll see if I can update this answer with working code for Autofac that follows his 'better option'. First I want to carefully read this article. On quick reading it seems to me the Autofac people are promoting using the 'Service Locator' for handling lifetime scope. But according to Mark Seemann that is an anti-pattern, so I have some stuff to figure out... Any DI expert with an opinion?

于 2015-11-20T16:04:56.670 回答