14

我正在SimpleInjector用作我的 IoC 库。我DbContext根据网络请求注册,它工作正常。但是有一项任务是我在后台线程中运行的。所以,我在创建DbContext实例时遇到了问题。例如

  1. Service1有一个实例DbContext
  2. Service2有一个实例DbContext
  3. Service1Service2从后台线程运行。
  4. Service1获取一个实体并将其传递给Service2
  5. Service2使用该实体,但实体与DbContext

实际上问题就在这里:Service1.DbContextService2.DbContext.

似乎当我在 ASP.NET MVC 的单独线程中运行任务时,会为每个调用SimpleInjector创建一个新实例。DbContext虽然一些 IoC 库(例如StructureMap)对 per-thread-per-webrequest 有一种混合的生活方式,但似乎SimpleInjector没有。我对吗?

你有什么想法解决这个问题SimpleInjector吗?提前致谢。

编辑:

我的服务在这里:

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其他时间用户。

4

1 回答 1

17

似乎当我在 ASP.NET MVC 的单独线程中运行任务时,SimpleInjector 会为每个调用创建一个新的 DbContext 实例。

Simple Injector v1.5 及以下的生活方式的行为是在RegisterPerWebRequestWeb 请求上下文之外请求实例时返回瞬态实例(其中HttpContext.Current为 null)。返回瞬态实例是 Simple Injector 的设计缺陷,因为这很容易隐藏不当使用。Simple Injector 1.6 版将抛出异常而不是错误地返回瞬态实例,以清楚地传达您错误配置了容器。

虽然一些 IoC 库(例如 StructureMap)对 per-thread-per-webrequest 有一种混合的生活方式,但似乎 Simple Injector 没有

由于几个原因,Simple Injector 没有对混合生活方式的内置支持是正确的。首先,它是一个非常奇特的功能,没有多少人需要。其次,您可以将任何两种或三种生活方式混合在一起,这样几乎是无穷无尽的混合体组合。最后,(非常)容易自己注册。

尽管您可以将Per Web RequestPer Thread生活方式混合使用,但将 Per Web Request 与Per Lifetime Scope混合使用可能会更好,因为使用 Lifetime Scope 您可以显式地开始和结束范围(并且可以DbContext在范围结束时处理) .

Simple Injector 2开始,您可以使用Lifestyle.CreateHybrid方法轻松地将任意数量的生活方式混合在一起。这是一个例子:

var hybridLifestyle = Lifestyle.CreateHybrid(
    () => HttpContext.Current != null,
    new WebRequestLifestyle(),
    new LifetimeScopeLifestyle());

// Register as hybrid PerWebRequest / PerLifetimeScope.
container.Register<DbContext, MyDbContext>(hybridLifestyle);

还有另一个 Stackoverflow 问题更深入地探讨了这个主题,您可能想看看:Simple Injector: multi-threading in MVC3 ASP.NET

更新

关于你的更新。你快到了。在后台线程上运行的命令需要在Lifetime Scope内运行,因此您必须显式启动它。这里的技巧是调用BeginLifetimeScope新线程,但在创建实际的命令处理程序(及其依赖项)之前。换句话说,最好的方法是在装饰器内部。

最简单的解决方案是更新您AsyncCommandHandlerDecorator以添加范围:

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的,如下所示:

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必须先注册:

container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(LifetimeScopedCommandHandlerDecorator<>),
    backgroundCommandCondition);

container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(AsyncCommandHandlerDecorator<>),
    backgroundCommandCondition);

这个旧的 Stackoverflow 问题更详细地讨论了这一点。你一定要看看。

于 2012-10-30T21:39:57.800 回答