牢牢地依赖DbContext实例是否不好?

时间:2018-08-14 19:26:39

标签: c# entity-framework dependency-injection asp.net-mvc-5 autofac

也许这个问题有一些解释,但找不到最佳解决方案:

我正在阅读Mark Seemann的blog post关于俘获依赖性的信息,据我了解,在文章结尾处,他得出的结论是从不使用或至少尝试避免俘获依赖性,否则将会出现麻烦(到目前为止,还可以)。这是Autofac文档中的另一个post

他们建议仅通过 目的 知道您在做什么!)来使用俘虏依赖项。这使我想到了我网站上的情况。我有10种服务,它们全部依靠DbContext进行数据库操作。我认为,如果我解决了InstancePerLifetimeScope的问题,而不是将其永久保存在附加到我的服务的内存中,则可以轻松将它们注册为DbContext。 (我正在使用Autofac)。因此,我认为一个好的出发点是根据生命周期实例创建所有这些,并根据请求创建DbContext作为实例。然后在我的服务中,我会使用类似的东西:

public class MyService
{
    private readonly IDbContext _dbContext = DependencyResolver.Current.GetService<IDbContext>();

    private MyModel GetMyModel() => Mapper.Map<MyModel>(_dbContext.MyTable.FirstOrDefault());
}

然后在我的启动课程中,我有:

builder.RegisterType<ApplicationDbContext>().As<IDbContext>().InstancePerRequest();
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();

此模式是否正常工作,我的意思是不要将dbContext永远附加在任何服务上,因此它将在请求的末尾处理,并且如果有效,此行是否存在任何性能问题:

 private readonly IDbContext _dbContext = DependencyResolver.Current.GetService<IDbContext>();

与构造函数注入相比(从dbContext到数据库的调用很多,因此每次我想使用IDbContext都会很麻烦,因为它可能会消耗资源)?

我希望dbContext是每个请求的实例而不是每个依赖项的实例的原因是,我已经在dbContext对象的顶部实现了工作单元模式。

控制器中的普通方法如下:

public ActionResult DoSth()
{
     using(var unitOfWork = UnitOfWorkManager.NewUnitOfWork())
     { 
          //do stuff
          try
          {
              unitOfWork.Commit();
              return View();
          }
          catch(Exception e)
          {
              unitOfWork.RollBack();
              LoggerService.Log(e);
              return View();
          }
     }
}

如果此方法正常,那么我还要担心另一个问题。因此,如果我可以使我的服务成为每个生命周期的实例(,除了DbContext ),那么对服务内部的每个方法应用async-await使其成为非阻塞方法是否存在任何问题。我要问的是,对于dbContext实例,使用async-await是否存在任何问题,因此,例如,我会有这样的事情:

public async MyModel GetMyModel()
{
   var result = //await on a new task which will use dbcontext instance here
   return Mapper.Map<MyModel>(result);
}

任何建议或建议都非常感谢!

2 个答案:

答案 0 :(得分:1)

我会从远处解决这个问题。

有些建筑选择可以使您的生活更轻松。在Web开发中,将应用程序设计为具有无状态服务层(所有状态都保留在DB中)并符合一个HTTP请求,一个业务操作原则(换句话说,一种针对一种控制器动作的服务方法。

我不知道您的体系结构如何(您的帖子中没有足够的信息来确定),但是可能满足我上面描述的标准。

在这种情况下,很容易决定选择哪个组件寿命:DbContext和服务类可以是临时的(在Autofac术语中是 InstancePerDependency ),也可以是每个请求( InstancePerRequest ) -没关系。关键是它们具有相同的生存期,因此完全不会出现圈养依赖性的问题

上述内容的进一步含义:

  • 您可以在服务类中仅使用ctor注入而不必担心。 (无论如何,在研究诸如lifetime scopes and IOwned<T>之类的生命周期控制可能性之后,服务定位器模式将是最后的选择。)

  • EF本身通过 SaveChanges 实现工作单元模式,适用于大多数情况。实际上,仅当基于某种原因的事务处理不能满足您的需求时,才需要通过EF实现UoW。这些是非常特殊的情况。

  

[...]是否存在将async-await应用于内部的每个方法的任何问题   服务以使其成为非阻塞方法。

如果您一直一致地应用异步等待模式(我的意思是所有异步操作都在等待),直接应用于您的控制器动作(返回 Task 而不是 ActionResult ),不会有任何问题。 (但是,请记住,在ASP.NET MVC 5中,异步支持并不完整-async child actions are not supported。)

答案 1 :(得分:0)

答案一如既往地取决于...在以下情况下,此配置可以正常工作

  1. 您的范围是在请求边界内创建的。您的工作单位是否在创建范围?
  2. 在创建范围之前,您不会解析任何InstancePerLifetimeScope服务。否则,它们的寿命可能会比您在请求中创建多个范围的寿命长。

我个人只是建议做任何依赖于DbContext(直接或间接)InstancePerRequest的事情。瞬变也可以。您绝对希望一个工作单元中的所有内容都使用相同的DbContext。否则,使用Entity Framework的一级缓存,您可能会拥有不同的服务来检索相同的数据库记录,但是如果它们未使用相同的DbContext,则会对不同的内存中副本进行操作。在这种情况下,最后一次更新将获胜。

我不会在MyService中引用您的容器,只需构造函数将其注入即可。在您的域或业务逻辑中的容器引用应谨慎使用,并且只能作为最后的选择。