混合生活方式每个线程和每个Web请求与简单的喷油器(Mixed lifestyle for Per

2019-07-03 11:03发布

我使用SimpleInjector作为我的IoC库。 我注册DbContext按Web请求,它工作正常。 但我在后台线程中运行一个任务。 所以,我有一个问题创造DbContext实例。 例如

  1. Service1有一个实例DbContext
  2. Service2有一个实例DbContext
  3. Service1Service2从后台线程中运行。
  4. Service1取一个实体,并把它传递给Service2
  5. Service2使用了实体,但实体是从分离DbContext

其实问题就在这里: Service1.DbContext是从差异Service2.DbContext

看来,当我在ASP.NET MVC一个单独的线程运行任务, SimpleInjector创建的新实例DbContext每个呼叫。 尽管一些国际奥委会库(例如StructureMap )对每个线程每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其他时间。

Answer 1:

看来,当我在ASP.NET MVC一个单独的线程运行任务,SimpleInjector每个调用创建的DbContext的新实例。

该行为RegisterPerWebRequest简单喷油器v1.5和下面的生活方式是当实例请求Web请求的上下文之外返回一个瞬态的实例(其中HttpContext.Current为null)。 返回一个瞬态的实例在简单的喷油器设计缺陷,因为这很容易隐藏不当使用。 简单喷油器的1.6版本会抛出异常,而不是返回错误的一个瞬态的实例,沟通清楚,你有错误配置的容器。

尽管一些国际奥委会库(例如StructureMap)具有每线程每WebRequest的一个混合的生活方式,看似简单喷油器还没有一个

这是正确的,简单的喷油器没有内置在的,因为几个原因混生活方式的支持。 首先,它是一个很奇特的功能,没有多少人需要。 其次,你可以任意两种或三种生活方式混合在一起,所以这将是混合动力车的几乎无限的组合。 而在去年,它是(很)容易做这个注册自己。

虽然你可以混合每个Web请求与每个线程的生活方式,它可能会更好,当你混合每个Web请求与每终身范围 ,因为与终身范围你明确地开始和结束的范围(可以处置DbContext当范围结束)。

从简单的喷油器2和上,你可以轻松地搭配任何数量的使用共同的生活方式Lifestyle.CreateHybrid方法。 下面是一个例子:

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

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

还有另外一个问题,#2是进入这一问题的深一点,你可能想看一看: 简单的喷油器:多线程在ASP.NET MVC3

UPDATE

关于您的更新。 你几乎没有。 即在后台线程运行的命令需要终身范围内运行,所以你必须明确地启动它。 这里的技巧是调用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);

这个老问题#1关于这个更详细的会谈。 你一定要来看看。



文章来源: Mixed lifestyle for Per Thread and Per Web Request with Simple Injector