混合生活方式每个线程和每个Web请求与简单的喷油器 [英] Mixed lifestyle for Per Thread and Per Web Request with Simple Injector

查看:249
本文介绍了混合生活方式每个线程和每个Web请求与简单的喷油器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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


  1. 服务1 的实例的DbContext

  2. 客服2 的实例的DbContext

  3. 从后台线程
  4. 服务1 客服2 运行。

  5. 服务1 获取一个实体,并把它传递给客服2

  6. 客服2 使用该实体,而是实体从分离的DbContext

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

看来当我运行在ASP.NET MVC一个单独的线程任务, SimpleInjector 创建的DbContext 每个呼叫。尽管一些国际奥委会库(例如 StructureMap )具有每线程每WebRequest的一个混合的生活方式,似乎 SimpleInjector 有没有之一。我说得对不对?

你有什么想法解决 SimpleInjector 这个问题?
先谢谢了。

编辑:

我的服务在这里:

 类服务:IService1 {
    公共服务1(MyDbContext上下文){}
}一流的客服2:IService2 {
    公共服务2(MyDbContext上下文,IService1服务1){}
}类SyncServiceUsage {
    公共SyncServiceUsage(服务2服务2){
        //从HttpContext.Current使用服务2(和服务1和的DbContext)
    }
}类AsyncServiceUsage {
    公共AsyncServiceUsage(服务2服务2){
        //从后台线程使用服务2(和服务1和的DbContext)
    }
}公共类AsyncCommandHandlerDecorator< TCommand>
    :ICommandHandler< TCommand>其中,TCommand:ICommand的{    私人只读Func键< ICommandHandler< TCommand>> _厂;    公共AsyncCommandHandlerDecorator(Func键< ICommandHandler< TCommand>>厂){
        _factory =厂;
    }    公共无效手柄(TCommand命令){
        ThreadPool.QueueUserWorkItem(_ => {
            //在这个线程创建新处理程序。
            无功处理= _factory();
            handler.Handle(命令);
        });
    }
}无效InitializeSimpleInjector(){
    注册AsyncCommandHandlerDecorator的服务(实际上是命令)与异步启动
}

我的用户客服2 有时和 AsyncService2 其他时间。


解决方案

  

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


简单的喷油器V1.5的 RegisterPerWebRequest 生活方式及以下的行为是当实例请求的Web请求的上下文外面返回一个瞬时对象(其中 HttpContext.Current 为空)。返回一个瞬态的实例在简单的喷油器设计缺陷,因为这很容易隐藏不当使用。 简单注射器将抛出一个异常,而不是返回错误的一个短暂的1.6版例如,要沟通清楚,你有错误配置的容器中。


  

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


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

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

简单的注射器2 而上,你可以轻松地搭配任何数量生活方式一起使用 Lifestyle.CreateHybrid 方法。下面是一个例子:

VAR hybridLifestyle = Lifestyle.CreateHybrid(
    ()=> HttpContext.Current!= NULL,
    新WebRequestLifestyle(),
    新LifetimeScopeLifestyle());//注册混合动力PerWebRequest / PerLifetimeScope。
container.Register<的DbContext,MyDbContext>(hybridLifestyle);

有另外一个问题,#2是进入这一问题的深一点,你可能想看看:<一href=\"https://stackoverflow.com/questions/11041601/simple-injector-multi-threading-in-mvc3-asp-net\">Simple喷油器:多线程在ASP.NET MVC3

更新

关于您的更新。你几乎没有。即在后台线程运行的命令需要一个的终身范围内运行的的,所以你必须明确地启动它。这里的窍门就是调用 BeginLifetimeScope 在新的线程,但实际的命令处理程序(及其附属)之前创建。换句话说,要做到这一点的最好办法是一个装饰中。

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

公共类AsyncCommandHandlerDecorator&LT; TCommand&GT;
    :ICommandHandler&LT; TCommand&GT;其中,TCommand:ICommand的
{
    私人只读集装箱_container;
    私人只读Func键&LT; ICommandHandler&LT; TCommand&GT;&GT; _厂;    公共AsyncCommandHandlerDecorator(集装箱货柜,
        FUNC&LT; ICommandHandler&LT; TCommand&GT;&GT;厂)
    {
        _container =容器;
        _factory =厂;
    }    公共无效手柄(TCommand命令)
    {
        ThreadPool.QueueUserWorkItem(_ =&GT;
        {
            使用(_container.BeginLifetimeScope())
            {
                //在这个线程创建新处理器
                //和寿命范围内。
                无功处理= _factory();
                handler.Handle(命令);
            }
        });
    }
}

这主张 SOLID 原则会喊这个类是纯粹主义者违反单一职责原则,因为这两种装饰上运行一个新的线程指令,并启动一个新的生命周期范围。我也不太担心这一点,因为我认为有启动后台线程并启动了一生的范围(你不会使用一个没有其他反正)之间的密切关系。但尽管如此,你可以很容易地将 AsyncCommandHandlerDecorator 不变,并创建一个新的 LifetimeScopedCommandHandlerDecorator 如下:

公共类LifetimeScopedCommandHandlerDecorator&LT; TCommand&GT;
    :ICommandHandler&LT; TCommand&GT;其中,TCommand:ICommand的
{
    私人只读集装箱_container;
    私人只读Func键&LT; ICommandHandler&LT; TCommand&GT;&GT; _厂;    公共LifetimeScopedCommandHandlerDecorator(集装箱货柜,
        FUNC&LT; ICommandHandler&LT; TCommand&GT;&GT;厂)
    {
        _container =容器;
        _factory =厂;
    }    公共无效手柄(TCommand命令)
    {
        使用(_container.BeginLifetimeScope())
        {
            //处理程序必须的寿命范围内创建。
            无功处理= _factory();
            handler.Handle(命令);
        }
    }
}

在这些装饰是注册的顺序当然是必不可少的,因为 AsyncCommandHandlerDecorator 必须包裹 LifetimeScopedCommandHandlerDecorator 。这意味着 LifetimeScopedCommandHandlerDecorator 注册必须先来:

container.RegisterDecorator(typeof运算(ICommandHandler&LT;&GT;)
    typeof运算(LifetimeScopedCommandHandlerDecorator&LT;&GT;)
    backgroundCommandCondition);container.RegisterDecorator(typeof运算(ICommandHandler&LT;&GT;)
    typeof运算(AsyncCommandHandlerDecorator&LT;&GT;)
    backgroundCommandCondition);

<一个href=\"https://stackoverflow.com/questions/10304023/simpleinjector-is-this-the-right-way-to-registermanyforopengeneric-when-i-have\">This老问题#1这个更详细会谈。你一定要来看看。

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.

  1. Service1 has an instance of DbContext
  2. Service2 has an instance of DbContext
  3. Service1 and Service2 run from background thread.
  4. Service1 fetches an entity and pass it to Service2
  5. 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.

这篇关于混合生活方式每个线程和每个Web请求与简单的喷油器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆