当通过依赖注入使用异步控制器时,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不受处理请求的线程更改的影响?

当通过依赖注入使用异步控制器时,dbContext的生命周期

InstancePerRequest不会受到处理请求的线程更改的影响,因为它不会在线程内部存储任何东西。如果您阅读InstancePerRequest()方法的源代码,您将看到它只是用一个众所周知的作用域标签调用InstancePerMatchingLifetimeScope

另一方面,在AutoFac.Mvc项目中,您将看到,在RequestLifetimeScopeProvider类中,它将范围存储在HttpContext中,这对执行线程的更改是免疫的。

唯一剩下的问题是:您的过滤器是否在启动请求范围的AutoFac过滤器的范围内执行?最可靠的方法是确保你的UnitOfWork被注射到你的过滤器中。假设是这样,您可以确信它是在请求的其余部分中存在的同一个UnitOfWork

我同意其他人的建议,不要在这样的过滤器中提交UnitOfWork (DbContext)。其中一组原因你已经在这个答案中读到过了。另一个原因是,它暗示(对我来说)你选择的模式是永远不要从你的单个服务方法内部调用SaveChanges(),而是等到最后才自动提交所有内容。这将工作得很好,直到它不工作的时刻,例如当EF更新或插入您的实体以错误的顺序。在这一点上,你将把自己画进一个角落。

我也可以假设你这样做是为了避免显式使用事务的头痛,因为如果你只调用SaveChanges()一次,所有的更新将发生在SaveChanges()内部的一个单独的、孤立的事务中,因此你可以避免这种痛苦。在EF中从不使用显式事务的想法也是一个陷阱,一旦你的应用程序发展到任何实质性的复杂性,尽管在网上的各种文章中"流行"拥护。我建议您规划您的基础设施以适应它。幸运的是,如果你使用像AutoFac这样的东西,你可以相当透明地将一个事务附加到你的请求范围(全有或全无的方法),或者在运行时创建一个包含事务的新范围,以便在你有选择地需要一个事务时使用。

一个可能的解决方案是在没有事务的情况下调用只读服务调用(以避免任何DB锁定),并且只在知道要写入数据库时才使用事务。