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
has an instance of DbContext
Service2
has an instance of DbContext
Service1
and Service2
run from background thread. Service1
fetches an entity and pass it to Service2
Service2
uses that entity, but entity is detached from DbContext
Actually the problem is here: Service1.DbContext
is difference from Service2.DbContext
.
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?
Have you any idea to solve this problem in SimpleInjector
?
Thanks in advance.
EDIT:
My services are here:
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"
}
I user Service2
sometimes and AsyncService2
other times.
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.
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.
While some IoC libraries (for example StructureMap) have a mixed lifestyle for per-thread-per-webrequest, it seems Simple Injector hasn't one
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.
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).
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);
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
UPDATE
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.
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);
}
});
}
}
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);
}
}
}
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);
This old Stackoverflow question talks about this in more detail. You should definitely take a look.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With