c# - injection - Estilo de vida mixto para Per Thread y Per Web Request with Simple Injector
simple injector container (1)
Parece que cuando ejecuto una tarea en un hilo separado en ASP.NET MVC, SimpleInjector crea una nueva instancia de DbContext para cada llamada.
El comportamiento del estilo de vida RegisterPerWebRequest
de Simple Injector v1.5 y siguientes es devolver una instancia transitoria cuando las instancias se solicitan fuera del contexto de una solicitud web (donde HttpContext.Current
es nulo). Devolver una instancia transitoria fue una falla de diseño en Simple Injector, ya que esto hace que sea fácil ocultar el uso incorrecto. La versión 1.6 del inyector simple generará una excepción en lugar de devolver incorrectamente una instancia transitoria, para comunicar claramente que ha configurado incorrectamente el contenedor.
Si bien algunas bibliotecas de IoC (por ejemplo, StructureMap) tienen un estilo de vida mixto por subproceso por búsqueda de hilos, parece que Simple Injector no tiene una
Es correcto que Simple Injector no tiene soporte integrado para estilos de vida mixtos debido a un par de razones. En primer lugar, es una característica bastante exótica que no muchas personas necesitan. En segundo lugar, puede mezclar dos o tres estilos de vida juntos, por lo que sería casi una combinación interminable de híbridos. Y por último, es (bastante) fácil registrar esto usted mismo.
Aunque puede mezclar Por solicitud web con estilos de vida Per Thread , probablemente sería mejor cuando mezcle Per Web Request con Per Lifetime Scope , ya que con Lifetime Scope inicia y finaliza explícitamente el alcance (y puede eliminar el DbContext
cuando finaliza el alcance) )
Desde Simple Injector 2 y posteriores , puede mezclar fácilmente cualquier cantidad de estilos de vida utilizando el método Lifestyle.CreateHybrid . Aquí hay un ejemplo:
var hybridLifestyle = Lifestyle.CreateHybrid(
() => HttpContext.Current != null,
new WebRequestLifestyle(),
new LifetimeScopeLifestyle());
// Register as hybrid PerWebRequest / PerLifetimeScope.
container.Register<DbContext, MyDbContext>(hybridLifestyle);
Hay otra pregunta de Stackoverflow que entra en este tema un poco más profundo, es posible que desee echar un vistazo: Inyector simple: multi-threading en MVC3 ASP.NET
ACTUALIZAR
Acerca de su actualización Ya casi has llegado. Los comandos que se ejecutan en un subproceso en segundo plano deben ejecutarse dentro de un alcance de por vida , por lo que tendrá que iniciarlo explícitamente. El truco aquí es llamar a BeginLifetimeScope
en el nuevo hilo, pero antes de que se BeginLifetimeScope
el controlador de comando real (y sus dependencias). En otras palabras, la mejor manera de hacerlo es dentro de un decorador.
La solución más fácil es actualizar su AsyncCommandHandlerDecorator
para agregar el alcance:
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);
}
});
}
}
Los puristas que abogan por los principios SÓLIDOS gritarán que esta clase está violando el Principio de Responsabilidad Individual , ya que este decorador ejecuta comandos en un nuevo hilo y comienza un nuevo alcance de por vida. No me preocuparía mucho por esto, ya que creo que existe una estrecha relación entre el inicio de un hilo de fondo y el inicio de un alcance de por vida (no usarías uno sin el otro de todos modos). Pero aún así, podría dejar intacto el AsyncCommandHandlerDecorator
y crear un nuevo LifetimeScopedCommandHandlerDecorator
siguiente manera:
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);
}
}
}
El orden en que se registran estos decoradores es, por supuesto, esencial, ya que AsyncCommandHandlerDecorator
debe envolver LifetimeScopedCommandHandlerDecorator
. Esto significa que el registro de LifetimeScopedCommandHandlerDecorator
debe ser lo primero:
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(LifetimeScopedCommandHandlerDecorator<>),
backgroundCommandCondition);
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(AsyncCommandHandlerDecorator<>),
backgroundCommandCondition);
Esta vieja pregunta de Stackoverflow habla de esto con más detalle. Definitivamente deberías echarle un vistazo.
Estoy usando SimpleInjector
como mi biblioteca de IoC. Registro DbContext
según la solicitud web y funciona bien. Pero hay una tarea que ejecutar en un hilo de fondo. Entonces, tengo un problema para crear instancias de DbContext
. p.ej
-
Service1
tiene una instancia deDbContext
-
Service2
tiene una instancia deDbContext
-
Service1
yService2
ejecutan desde el hilo de fondo. -
Service1
recupera una entidad y la pasa aService2
-
Service2
usa esa entidad, pero la entidad está separada deDbContext
En realidad, el problema está aquí: Service1.DbContext
es la diferencia de Service2.DbContext
.
Parece que cuando ejecuto una tarea en un hilo separado en ASP.NET MVC, SimpleInjector
crea una nueva instancia de DbContext
para cada llamada. Mientras que algunas bibliotecas de IoC (por ejemplo, StructureMap
) tienen un estilo de vida mixto por subproceso por cada búsqueda, parece que SimpleInjector
no tiene una. ¿Estoy en lo cierto?
¿Tienes alguna idea de resolver este problema en SimpleInjector
? Gracias por adelantado.
EDITAR:
Mis servicios estan aqui
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"
}
AsyncService2
veces y AsyncService2
otras veces.