【问题标题】:Autofac (+MVC + EF + SignalR + Hangfire) lifetime scopesAutofac (+MVC + EF + SignalR + Hangfire) 生命周期范围
【发布时间】:2015-03-16 20:17:25
【问题描述】:

我有一个使用实体框架、SignalR 和 Hangfire 作业的 ASP.NET MVC 项目。

我的主(根)容器是这样定义的:

builder.RegisterType<DbContext>().InstancePerLifetimeScope(); // EF Db Context
builder.RegisterType<ChatService>().As<IChatService>().SingleInstance(); // classic "service", has dependency on DbContext
builder.RegisterType<ChatHub>().ExternallyOwned(); // SignalR hub
builder.RegisterType<UpdateStatusesJob>().InstancePerDependency(); // Hangfire job
builder.RegisterType<HomeController>().InstancePerRequest(); // ASP.NET MVC controller
IContainer container = builder.Build();

对于 MVC,我使用 Autofac.MVC5 nuget 包。依赖解析器:

DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

对于 SignalR,我使用的是 Autofac.SignalR nuget 包。依赖解析器:

GlobalHost.DependencyResolver = new Autofac.Integration.SignalR.AutofacDependencyResolver(container);

我的 signalR 集线器以这种方式实例化 (http://autofac.readthedocs.org/en/latest/integration/signalr.html#managing-dependency-lifetimes):

private ILifetimeScope _hubScope;
protected IChatService ChatService;
public ChatHub(ILifetimeScope scope) {
  _hubScope = scope.BeginLifetimeScope(); // scope 
  ChatService = _hubScope.Resolve<IChatService>(); // this service is used in hub methods
}
protected override void Dispose(bool disposing)
{
  // Dipose the hub lifetime scope when the hub is disposed.
  if (disposing && _hubScope != null)
  {
    _hubScope.Dispose();
  }
  base.Dispose(disposing);
}

对于 Hangfire,我使用的是 Hangfire.Autofac 包:

config.UseActivator(new AutofacJobActivator(container));

作业以这种方式实例化:

private readonly ILifetimeScope _jobScope;
protected IChatService ChatService;
protected BaseJob(ILifetimeScope scope)
{
    _jobScope = scope.BeginLifetimeScope();
    ChatService = _jobScope.Resolve<IChatService>();
}
public void Dispose()
{
    _jobScope.Dispose();
}

问题/问题: 我总是在集线器和作业中获得相同的 DbContext 实例。我希望所有集线器实例都将获得相同的 ChatService,但 DbContext(它是 ChatService 的依赖项)将始终是一个新实例。此外,Hangfire 作业也应如此。

这可以做到吗,还是我错过了什么?

更新 1:

经过思考(和睡过头),我想我有两个选择。我仍然想保留“每个请求的会话”(“每个中心的会话”、“每个作业的会话”)。

选项 1:

更改所有服务都将具有 InstancePerLifetimeScope。服务的实例化并不昂贵。对于维护某种状态的服务,我将创建另一个“存储”(类),它将是 SingleInstance 并且不会依赖于会话(DbContext)。我认为这也适用于中心和工作。

选项 2:

创建@Ric .Net 建议的某种工厂。像这样的:

public class DbFactory: IDbFactory
{
    public MyDbContext GetDb()
    {
        if (HttpContext.Current != null)
        {
            var db = HttpContext.Current.Items["db"] as MyDbContext;
            if (db == null)
            {
                db = new MyDbContext();
                HttpContext.Current.Items["db"] = db;
            }
            return db;
        }

        // What to do for jobs and hubs?
        return new MyDbContext();
    }
}

    protected void Application_EndRequest(object sender, EventArgs e)
    {
        var db = HttpContext.Current.Items["db"] as MyDbContext;
        if (db != null)
        {
            db.Dispose();
        }
    }

我认为这适用于 MVC,但我不知道如何让它适用于集线器(每个集线器调用都是集线器的新实例)和作业(每次运行作业都是工作类)。

我倾向于选项 1。你怎么看?

非常感谢!

【问题讨论】:

  • 你不应该乱用范围。事实上,您根本不需要将它传递给您的构造函数。您的服务应该通过 DI 容器传递,并让 autofac 处理您的范围。
  • 您还应该仔细检查文档。 具有每个生命周期范围的组件在每个嵌套生命周期范围内最多有一个实例。
  • @TheVedge:我同意你的看法。建议的样式(将容器传递给构造函数)来自文档:autofac.readthedocs.org/en/latest/integration/…

标签: asp.net-mvc entity-framework signalr autofac hangfire


【解决方案1】:

我对 AutoFac 完全没有经验。但引起我注意的是:

我希望所有集线器实例都获得相同的 ChatService,但 DbContext(它是 ChatService 的依赖项)将始终是一个新实例。

你在这里的基本意思是:

“我的车正在由依赖他们车库的同一家汽车公司进行维护,但每次我带我的车时,我都希望车库是一个新的”。

当您将ChatService 的(完全构建实例,包括依赖项)注入到其他一些组件中时,当然也会构建它所具有的其他依赖项,无论它们是否具有其他生活方式。当创建一个生命周期比它注入的对象更短的对象时,您创建了一个所谓的“captive dependency

ChatService 中获得DbContext 的新“实例”的唯一方法不是注入DbContext 本身,而是注入DbContextFactory,它会在您使用时为您创建DbContext它。

实现类似于:

public class DbContextFactory
{
    public DbContext Create()
    {
         return new DbContext();
    }
}

//usage:
public class ChatService
{
     private readonly DbContextFactory dbContextFactory;

     public ChatService(DbContextFactory dbContextFactory)
     {
         this.dbContextFactory = dbContextFactory;
     }

    public void SomeMethodInChatService()
    {
         using (var db = this.dbContextFactory.Create())
         {
             //do something with DbContext    
         }
     }
}

DbContextFactory 可以使用 Singleton Lifestyle 在 AutoFac 中注册。

但这可能不是您的目标。因为在这种情况下每次你使用DbContext 你都会得到一个新的。另一方面,新的 DbContext 可能是解决此问题的最安全方法,您可以阅读 here

这个很好的答案值得一读的原因不止一个,因为它解释了如何使用command / handler pattern,它应该非常适合您的情况。

这将使您的聊天服务完全不知道DbContext,这会改进您的应用程序的“SOLID”设计并创建测试ChatService 的可能性,这在注入DbContext 时实际上是不可撤销的或直接DbContextFactory

【讨论】:

  • @MattKocaj 我编辑了答案,因为生活方式是依赖注入中生命周期管理的正确术语。
  • “生活方式”不是正确的术语。只需谷歌“依赖注入”,您就会发现使用的术语是 "lifetime""lifecycle"。我认为“生活方式”是一个错字。
  • @MattKocaj DI bible 指的是使用不同的“生活方式”进行生命周期管理。
  • 虽然 Seemann 使用“*style”作为他的模式集合,但上述模式(单例、瞬态等)的实现可能会因容器而异。这个问题是关于 Autofac 的,所以对于特定于该库的上下文,以及为了其他没有读过 Seemann 的书的读者,我建议你使用一个最相关的术语,这将有助于更广泛的受众,IMO 是“生命周期”或“生命周期”——Autofac doco 也很容易使用的术语。你不会在autofac.readthedocs.io 中找到“生活方式”,我觉得这是个问题。
  • @MattKocaj:我不同意你的看法。 Seemann 的书已成为有关 DI 模式的事实上的标准参考。因此,我强烈建议继续使用常用术语,就像我们将继续使用常用的模式名称(例如装饰器和代理)一样。
【解决方案2】:

你需要解析一个工厂。 Autofac 内置了对 Func&lt;T&gt; 的支持,例如参见 Dynamic instantiation

如果您的依赖项具有 disposable 依赖项,则必须管理 dispose 模式以避免内存泄漏。使用 Autofac 解决此问题的常见模式是使用 Func&lt;Owned&lt;T&gt;&gt;

public class ChatService
{
    public ChatService(Func<Owned<DbContext>> dbContextFactory)
    {
        this._dbContextFactory = dbContextFactory;
    }

    private readonly Func<Owned<DbContext>> _dbContextFactory;

    private void DoSomething()
    {
        using (Owned<DbContext> ownedDbContext = this._dbContextFactory())
        {
            DbContext context = ownedDbContext.Value;
        }
    }
}

Func&lt;T&gt; 是一个工厂。每次调用 factory 时,autofac 都会返回一个新实例(取决于如何配置注册的生命周期)。 Owned&lt;T&gt; 是一个轻量级的ILifetimescope,这个类的主要目的是管理已解析组件的处置。

您可以在此处找到有关Func&lt;Owned&lt;T&gt;&gt; 的更多信息:Combining Owned&lt;T&gt; with Func&lt;T&gt;

【讨论】:

  • Owned&lt;T&gt; 接口的缺点是您的应用程序代码依赖于 DI 库,这是您通常希望阻止 IMO 的事情。
  • @Steven 我同意你的看法。可以解决Func&lt;DbContext&gt;,但不能保证在处置DbContext 时会处置DbContext 的直接或间接一次性依赖项。据我所知,没有与Owned&lt;T&gt; 等效的本机.net。我编辑了帖子以更好地解释Owned&lt;T&gt;
  • 也许我误解了 Autofac 的工作原理,但使用 Owned&lt;T&gt; 似乎是多余的。如果DbContext 注册为InstancePerLifetimeScope,Autofac 应该在其周围的生命周期结束时自动处理DbContextinstance。换句话说,注入一个Func&lt;DbContext&gt; 就足够了。然而 Autofac 的问题是,如果没有活动的生命周期范围,DbContext 将被解析为单例(IMO 设计缺陷)。这让它有点棘手。
  • 如果这些对象的生命周期相同,则 Owned&lt;T&gt; 是多余的。但是如果工厂的所有者是单例,工厂创建的对象图不会被释放(GC可能会释放它们)。在这种情况下,ChatService 似乎是一个单例。
  • 对不起。我的假设是这样可行,因为Func&lt;DbContext&gt; 是单例,就像ChatService 一样。我的假设是Func&lt;DbContext&gt; 会根据生活方式生成一个新实例,但经过一些实验,我不得不得出结论,Func&lt;T&gt; 在注入单例时表现不同。 Autofac 中的 Func&lt;T&gt; 在注入单例时会生成单例,即使在生命周期范围的 上下文 中调用 DoSomething 也是如此。这真的让 IMO 感到困惑。但这解释了为什么在 Autofac 的情况下你必须依赖 Owned&lt;T&gt;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-19
  • 2020-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多