当通过依赖注入使用异步控制器时,dbContext的生命周期
本文关键字:控制器 dbContext 周期 生命 异步控制 异步 依赖 注入 | 更新日期: 2023-09-27 18:10:10
在过去,我使用AutoFac在InstancePerRequest调度上向各种服务注入EntityFramework DB上下文。
builder.RegisterType<MyDataContext>()
.As<IDataContext>()
.As<IUnitOfWork>()
.InstancePerRequest();
这允许我在向控制器中注入多个服务时跨服务共享上下文。
// Note each of these services take a IDataContext via constructor injection
public FilesController(
IAnalysisService analysisService,
IUserService userService)
{
}
我有一个动作过滤器,在每个动作请求结束时提交我的上下文(我可能正在考虑改变这一点,但现在它适合我这个问题的目的)。
public class UnitOfWorkActionAttribute : ActionFilterAttribute
{
public IUnitOfWork UnitOfWork { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
UnitOfWork.SaveChangesAsync().WaitAndUnwrapException();
}
}
我对MVC中异步的理解是,启动请求的线程不能保证完成它,因为它在使用await
时被释放。
在AutoFac注入中,这是否意味着注入到ActionFilterAttribute中的UnitOfWork实例可能与注入到控制器中的实例不同,或者InstancePerRequest不受处理请求的线程更改的影响?
InstancePerRequest
不会受到处理请求的线程更改的影响,因为它不会在线程内部存储任何东西。如果您阅读InstancePerRequest()
方法的源代码,您将看到它只是用一个众所周知的作用域标签调用InstancePerMatchingLifetimeScope
。
另一方面,在AutoFac.Mvc
项目中,您将看到,在RequestLifetimeScopeProvider
类中,它将范围存储在HttpContext中,这对执行线程的更改是免疫的。
唯一剩下的问题是:您的过滤器是否在启动请求范围的AutoFac过滤器的范围内执行?最可靠的方法是确保你的UnitOfWork
被注射到你的过滤器中。假设是这样,您可以确信它是在请求的其余部分中存在的同一个UnitOfWork
。
UnitOfWork
(DbContext
)。其中一组原因你已经在这个答案中读到过了。另一个原因是,它暗示(对我来说)你选择的模式是永远不要从你的单个服务方法内部调用SaveChanges()
,而是等到最后才自动提交所有内容。这将工作得很好,直到它不工作的时刻,例如当EF更新或插入您的实体以错误的顺序。在这一点上,你将把自己画进一个角落。
我也可以假设你这样做是为了避免显式使用事务的头痛,因为如果你只调用SaveChanges()
一次,所有的更新将发生在SaveChanges()
内部的一个单独的、孤立的事务中,因此你可以避免这种痛苦。在EF中从不使用显式事务的想法也是一个陷阱,一旦你的应用程序发展到任何实质性的复杂性,尽管在网上的各种文章中"流行"拥护。我建议您规划您的基础设施以适应它。幸运的是,如果你使用像AutoFac这样的东西,你可以相当透明地将一个事务附加到你的请求范围(全有或全无的方法),或者在运行时创建一个包含事务的新范围,以便在你有选择地需要一个事务时使用。
一个可能的解决方案是在没有事务的情况下调用只读服务调用(以避免任何DB锁定),并且只在知道要写入数据库时才使用事务。