问题描述
我使用 SimpleInjector
作为我的 IoC 库.我根据网络请求注册 DbContext
并且它工作正常.但是有一项任务是我在后台线程中运行的.所以,我在创建 DbContext
实例时遇到了问题.例如
I'm using SimpleInjector
as my IoC library. I register DbContext
as per web request and it works fine. But there is one task that I run it in a background thread. So, I have a problem to create DbContext
instances. e.g.
Service1
有一个DbContext
的实例Service2
有一个DbContext
的实例Service1
和Service2
从后台线程运行.Service1
获取实体并将其传递给Service2
Service2
使用该实体,但实体与DbContext
分离
Service1
has an instance ofDbContext
Service2
has an instance ofDbContext
Service1
andService2
run from background thread.Service1
fetches an entity and pass it toService2
Service2
uses that entity, but entity is detached fromDbContext
其实问题出在这里:Service1.DbContext
与 Service2.DbContext
不同.
Actually the problem is here: Service1.DbContext
is difference from Service2.DbContext
.
似乎当我在 ASP.NET MVC 中的单独线程中运行任务时,SimpleInjector
为每次调用创建一个新的 DbContext
实例.虽然一些 IoC 库(例如 StructureMap
)对于 per-thread-per-webrequest 具有混合的生活方式,但似乎 SimpleInjector
没有.我说得对吗?
It seems when I run a task in a separate thread in ASP.NET MVC, SimpleInjector
creates a new instance of DbContext
for each call. While some IoC libraries (for example StructureMap
) have a mixed lifestyle for per-thread-per-webrequest, it seems SimpleInjector
hasn't one. Am I right?
你有没有办法在SimpleInjector
中解决这个问题?提前致谢.
Have you any idea to solve this problem in SimpleInjector
?Thanks in advance.
我的服务在这里:
class Service1 : IService1 {
public Service1(MyDbContext context) { }
}
class Service2 : IService2 {
public Service2(MyDbContext context, IService1 service1) { }
}
class SyncServiceUsage {
public SyncServiceUsage(Service2 service2) {
// use Service2 (and Service1 and DbContext) from HttpContext.Current
}
}
class AsyncServiceUsage {
public AsyncServiceUsage(Service2 service2) {
// use Service2 (and Service1 and DbContext) from background thread
}
}
public class AsyncCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand> where TCommand : ICommand {
private readonly Func<ICommandHandler<TCommand>> _factory;
public AsyncCommandHandlerDecorator(Func<ICommandHandler<TCommand>> factory) {
_factory = factory;
}
public void Handle(TCommand command) {
ThreadPool.QueueUserWorkItem(_ => {
// Create new handler in this thread.
var handler = _factory();
handler.Handle(command);
});
}
}
void InitializeSimpleInjector() {
register AsyncCommandHandlerDecorator for services (commands actually) that starts with "Async"
}
我有时使用 Service2
,有时使用 AsyncService2
.
I user Service2
sometimes and AsyncService2
other times.
推荐答案
Simple Injector v1.5 及以下版本的 RegisterPerWebRequest
生活方式的行为是在 Web 请求上下文之外请求实例时返回一个瞬态实例(其中 HttpContext.Current
为空).返回一个瞬态实例是 Simple Injector 的一个设计缺陷,因为这很容易隐藏不当使用.1.6 版 Simple Injector 将抛出异常而不是错误地返回瞬态实例,清楚地表明您错误地配置了容器.
The behavior of the RegisterPerWebRequest
lifestyle of Simple Injector v1.5 and below is to return a transient instance when instances are requested outside the context of a web request (where HttpContext.Current
is null). Returning a transient instance was a design flaw in Simple Injector, since this makes it easy to hide improper usage. Version 1.6 of the Simple Injector will throw an exception instead of incorrectly returning a transient instance, to communicate clearly that you have mis-configured the container.
虽然一些 IoC 库(例如 StructureMap)有一个混合的per-thread-per-webrequest 的生活方式,似乎是 Simple Injector没有
由于几个原因,Simple Injector 没有内置对混合生活方式的支持是正确的.首先,这是一个非常奇特的功能,并不是很多人需要的.其次,您可以将任意两种或三种生活方式混合在一起,这样几乎可以形成无穷无尽的混合体.最后,自己注册(非常)容易.
It is correct that Simple Injector has no built-in support for mixed lifestyles because of a couple reasons. First of all it's quite an exotic feature that not many people need. Second, you can mix any two or three lifestyles together, so that would be almost an endless combination of hybrids. And last, it is (pretty) easy do register this yourself.
虽然您可以将每个网络请求与Per Thread 生活方式,将 Per Web Request 与 ,因为使用 Lifetime Scope 您可以明确地开始和结束范围(和可以在作用域结束时处理DbContext
).
Although you can mix Per Web Request with Per Thread lifestyles, it would probably be better when you mix Per Web Request with Per Lifetime Scope, since with the Lifetime Scope you explicitly start and finish the scope (and can dispose the DbContext
when the scope ends).
从 Simple Injector 2 开始,您可以轻松地将任意数量的生活方式混合在一起使用 Lifestyle.CreateHybrid 方法.下面是一个例子:
From Simple Injector 2 and on, you can easily mix any number of lifestyles together using the Lifestyle.CreateHybrid method. Here is an example:
var hybridLifestyle = Lifestyle.CreateHybrid(
() => HttpContext.Current != null,
new WebRequestLifestyle(),
new LifetimeScopeLifestyle());
// Register as hybrid PerWebRequest / PerLifetimeScope.
container.Register<DbContext, MyDbContext>(hybridLifestyle);
还有另一个 Stackoverflow 问题更深入地探讨了这个主题,你可能想看看:简单注入器:MVC3 ASP.NET 中的多线程
There is another Stackoverflow question that goes into this subject a bit deeper, you might want to take a look: Simple Injector: multi-threading in MVC3 ASP.NET
更新
关于您的更新.你快到了.在后台线程上运行的命令需要在 Lifetime Scope 内运行,因此您必须明确启动它.这里的技巧是在新线程上调用 BeginLifetimeScope
,但在创建实际命令处理程序(及其依赖项)之前.换句话说,最好的方法是在装饰器中.
About your update. You are almost there. The commands that run on a background thread need to run within a Lifetime Scope, so you will have to start it explicitly. The trick here is to call BeginLifetimeScope
on the new thread, but before the actual command handler (and its dependencies) is created. In other words, the best way to do this is inside a decorator.
最简单的解决方案是更新您的 AsyncCommandHandlerDecorator
以添加范围:
The easiest solution is to update your AsyncCommandHandlerDecorator
to add the scope:
public class AsyncCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand> where TCommand : ICommand
{
private readonly Container _container;
private readonly Func<ICommandHandler<TCommand>> _factory;
public AsyncCommandHandlerDecorator(Container container,
Func<ICommandHandler<TCommand>> factory)
{
_container = container;
_factory = factory;
}
public void Handle(TCommand command)
{
ThreadPool.QueueUserWorkItem(_ =>
{
using (_container.BeginLifetimeScope())
{
// Create new handler in this thread
// and inside the lifetime scope.
var handler = _factory();
handler.Handle(command);
}
});
}
}
提倡SOLID原则的纯粹主义者会大喊这个类是违反了单一职责原则,因为这个装饰器在新线程上运行命令并开始新的生命周期范围.我不会太担心这个,因为我认为启动后台线程和启动生命周期范围之间存在密切的关系(无论如何你不会在没有另一个的情况下使用一个).但是,您仍然可以轻松地保持 AsyncCommandHandlerDecorator
不变并创建一个新的 LifetimeScopedCommandHandlerDecorator
,如下所示:
Purists that advocate the SOLID principles will shout that this class is violating the Single Responsibility Principle, since this decorator both runs commands on a new thread and starts a new lifetime scope. I wouldn't worry much about this, since I think that there is a close relationship between starting a background thread and starting a lifetime scope (you wouldn't use one without the other anyway). But still, you could easily leave the AsyncCommandHandlerDecorator
untouched and create a new LifetimeScopedCommandHandlerDecorator
as follows:
public class LifetimeScopedCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand> where TCommand : ICommand
{
private readonly Container _container;
private readonly Func<ICommandHandler<TCommand>> _factory;
public LifetimeScopedCommandHandlerDecorator(Container container,
Func<ICommandHandler<TCommand>> factory)
{
_container = container;
_factory = factory;
}
public void Handle(TCommand command)
{
using (_container.BeginLifetimeScope())
{
// The handler must be created inside the lifetime scope.
var handler = _factory();
handler.Handle(command);
}
}
}
这些装饰器的注册顺序当然是必不可少的,因为AsyncCommandHandlerDecorator
必须包装LifetimeScopedCommandHandlerDecorator
.这意味着必须先注册 LifetimeScopedCommandHandlerDecorator
:
The order in which these decorators are registered is of course essential, since the AsyncCommandHandlerDecorator
must wrap the LifetimeScopedCommandHandlerDecorator
. This means that the LifetimeScopedCommandHandlerDecorator
registration must come first:
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(LifetimeScopedCommandHandlerDecorator<>),
backgroundCommandCondition);
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(AsyncCommandHandlerDecorator<>),
backgroundCommandCondition);
这个旧的 Stackoverflow 问题 更详细地讨论这个.你绝对应该看看.
This old Stackoverflow question talks about this in more detail. You should definitely take a look.
这篇关于使用简单注入器实现每线程和每 Web 请求的混合生活方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!