我有一个ASP.NET MVC项目,它使用Entity Framwork,SignalR和Hangfire作业。
我的主(根)容器以这种方式定义:
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 hub以这种方式实例化(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并且不依赖于session(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.你怎么看?
非常感谢!
答案 0 :(得分:4)
我对AutoFac完全缺乏经验。但引起我注意的是:
我希望所有集线器实例都能获得相同的ChatService,但DbContext(它是ChatService的依赖项)将始终是一个新实例。
你在这里基本上说的是:
“我的汽车是由依赖车库的汽车公司进行维修的,但每次我带车时都希望车库成为新车”。
当您在其他组件中注入(完全构建实例,包括依赖项)ChatService
时,当然还会构建其他依赖项,无论它们是否具有其他类型的生活方式。当创建一个比它注入的对象更短的生命周期的对象时,你创建了一个所谓的“captive dependency”
在DbContext
中获取ChatService
的新“实例”的唯一方法不是注入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
时。
答案 1 :(得分:1)
您需要解决工厂问题。 Autofac 内置支持Func<T>
,例如Dynamic instantiation。
如果您的依赖项具有一次性依赖项,则必须管理dispose模式以避免内存泄漏。使用 Autofac 解决此问题的常见模式是使用Func<Owned<T>>
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<T>
是工厂。每次调用 factory 时,autofac都将返回一个新实例(具体取决于注册生命周期的配置方式)。
Owned<T>
是一个轻型ILifetimescope
,此类的主要目的是管理已解析组件的处理。
您可以在此处找到有关Func<Owned<T>>
的更多信息:Combining Owned<T>
with Func<T>