我有一个控制器:
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();
在这种情况下,第一个请求完成,但在尝试打开其他页面时,我再次收到错误。问题在哪里?
答案 0 :(得分:19)
使用WithParameter方法时,参数实例对于每个已解析的对象都是相同的。因此,对于.WithParameter("context", new PcpContext())
,您实际上正在为任何已解析的IRepository实例使用相同的PcpContext类实例。
使用当前代码,当处理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();
答案 1 :(得分:0)
我选择了@Daniel J.G建议的“代码块”的简单解决方案(一个lambda)。
下面是Autofac中的代码示例。丹尼尔斯的例子是Unity,因为他也提到了自己。因为OP将Autofac添加为标签,这似乎与我相关:
_builder.Register(c => new AppDbContext()).As(typeof(AppDbContext));
该代码解决了我在Entity Framework中遇到的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专家?