使用ScopedLifestyle创建作用域的装饰器.在简单注入器中流动 [英] Decorator for creating Scope with ScopedLifestyle.Flowing in Simple Injector

查看:77
本文介绍了使用ScopedLifestyle创建作用域的装饰器.在简单注入器中流动的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要一些帮助来了解我在配置容器时出了什么问题.

I need some help to understand what it's wrong in my configuration of the container.

我通过使用此示例.

基本上我需要基于该接口将一些用例实现为数据库命令

Basically i need to implement some use case as database command based on that interface

public interface IDatabaseCommand<TResult, TParam>
{
    TResult Execute(TParam commandParam);
}

并且我想使用添加事务安全功能的装饰器.

and i want to use a decorator that add the transaction safe functionality.

每个命令都需要使用专用的DbContext,并且必须在该上下文上执行事务

Every command need to use a dedicated DbContext and the transaction has to be executed on that context

为此,我已经实现了

事务装饰器:

public class TransactionDatabaseCommandDecorator 
    : IDatabaseCommand<DatabaseResult, BusinessCommandParams1>
{
    private readonly Container _container;
    private readonly Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>>
        _decorateeFactory;

    public TransactionDatabaseCommandDecorator(
        Container container,
        Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>> decorateeFactory)
    {
        _container = container;
        _decorateeFactory = decorateeFactory;
    }

    public DatabaseResult Execute(BusinessCommandParams1 commandParam)
    {
        DatabaseResult res;
        using (AsyncScopedLifestyle.BeginScope(_container))
        {
            var _command = _decorateeFactory.Invoke();

            var factory = _container
                .GetInstance<IDesignTimeDbContextFactory<WpfRadDispenserDbContext>>();

            using (var transaction = factory.CreateDbContext(
                new[] { "" }).Database.BeginTransaction())
            {
                try
                {
                    res = _command.Execute(commandParam);
                    transaction.Commit();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    transaction.Rollback();
                    throw;
                }
            }
        }

        return res;
    }
}

实施示例:

public class WpfRadDispenserUOW : IUnitOfWork<WpfRadDispenserDbContext>
{
    private readonly IDesignTimeDbContextFactory<WpfRadDispenserDbContext> _factory;
    private WpfRadDispenserDbContext _context;
    private IDbContextTransaction _transaction;
    public bool IsTransactionPresent => _transaction != null;

    public WpfRadDispenserUOW(IDesignTimeDbContextFactory<WpfRadDispenserDbContext> fact)
    {
        _factory = fact ?? throw new ArgumentNullException(nameof(fact));
    }

    public WpfRadDispenserDbContext GetDbContext() =>
         _context ?? (_context = _factory.CreateDbContext(null));

    public IDbContextTransaction GetTransaction() =>
        _transaction ?? (_transaction = GetDbContext().Database.BeginTransaction());

    public void RollBack()
    {
        _transaction?.Rollback();
        _transaction?.Dispose();
    }

    public void CreateTransaction(IsolationLevel isolationLevel) => GetTransaction();
    public void Commit() => _transaction?.Commit();
    public void Persist() => _context.SaveChanges();
    
    public void Dispose()
    {
        _transaction?.Dispose();
        _context?.Dispose();
    }
}

一些命令:

public class BusinessCommand1 : IDatabaseCommand<DatabaseResult, BusinessCommandParams1>
{
    private readonly IUnitOfWork<WpfRadDispenserDbContext> _context;

    public BusinessCommand1(IUnitOfWork<WpfRadDispenserDbContext> context)
    {
       _context = context;
    }

    public DatabaseResult Execute(BusinessCommandParams1 commandParam)
    {
        //ToDo: use context
        return new DatabaseResult();
    }
}

容器的注册

var container = new Container();
container.Options.DefaultScopedLifestyle = ScopedLifestyle.Flowing;

container.Register<IDesignTimeDbContextFactory<WpfRadDispenserDbContext>>(() =>
{
    var factory = new WpfRadDispenserDbContextFactory();
    factory.ConnectionString =
        "Server=.\\SqlExpress;Database=Test;Trusted_Connection=True";
    return factory;
});

container.Register<IUnitOfWork<WpfRadDispenserDbContext>, WpfRadDispenserUOW>(
    Lifestyle.Scoped);
container
    .Register<IUnitOfWorkFactory<WpfRadDispenserDbContext>, WpfRadDispenserUOWFactory>();

//Command registration
container.Register<
    IDatabaseCommand<DatabaseResult, BusinessCommandParams1>,
    BusinessCommand1>();

//Command Decorator registration
container.RegisterDecorator(
    typeof(IDatabaseCommand<DatabaseResult, BusinessCommandParams1>),
    typeof(TransactionDatabaseCommandDecorator),Lifestyle.Singleton);

问题是当我尝试执行时

var transactionCommandHandler =
    _container.GetInstance<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>>();
usecase.Execute(new BusinessCommandParams1());

我正确接收到 TransactionDatabaseCommandDecorator 的实例,但是当我尝试从工厂获取实例时,我收到此错误

i receive correctly an instance of TransactionDatabaseCommandDecorator but when the i try to get the instance from the factory i receive this error

SimpleInjector.ActivationException:WpfRadDispenserUOW是使用"Scoped"生活方式注册的,但是在活动(Scoped)作用域的上下文之外请求了该实例.有关如何应用生活方式和管理范围的更多信息,请参见 https://simpleinjector.org/scoped .>

SimpleInjector.Scope.GetScopelessInstance中的

in SimpleInjector.Scope.GetScopelessInstance(ScopedRegistration registration)
in SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration registration, Scope scope)
in SimpleInjector.Advanced.Internal.LazyScopedRegistration`1.GetInstance(Scope scope)
in WpfRadDispenser.DataLayer.Decorator.TransactionDatabaseCommandDecorator.Execute(BusinessCommandParams1 commandParam) in C:\Work\Git\AlphaProject\WpfRadDispenser\WpfRadDispenser.DataLayer\Decorator\TransactionDatabaseCommandDecorator.cs: riga 29
in WpfRadDispenser.Program.Main() in C:\Work\Git\AlphaProject\WpfRadDispenser\WpfRadDispenser\Program.cs: riga 47

这里的问题是我想使用由他的装饰器创建和控制的dbcontext.但是构造函数注入是由容器处理的,所以我如何在命令内部注入由装饰器创建的上下文?

The problem here is that i want to use a dbcontext that it's created and controlled by his decorator. But the constructor injection it's handled by container so how i can inject the context created by the decorator inside the command?

基本上我想拥有类似命令装饰者所做的东西

Basically i want to having something like that made by the decorator of the command

var context = ContextFactory.GetContext();

try
{
    var transaction = context.database.GetTransaction();
    var command = new Command(context);
    var commandParams = new CommandParams();
    var ret = command.Execute(commandParams);
    
    if (!ret.Success)
    {
        transaction.Discard();
        return;
    }
    
    transaction.Commit();
}
catch
{
    transaction.Discard();
}

但由DI和Simple Injector制成

but made with DI and Simple Injector

也许我的设计上有一个问题或几个问题,但是我是DI的新手,我想更好地了解它们的工作原理.

Maybe there is some issue or several issue on my design but i'm new on DI and i want to understand better how the things works.

回顾一下,我需要使用很多命令数据库,其中每个命令必须具有隔离的上下文,并且事务的功能必须由装饰器内部的额外层控制.

Just to recap i need to use a lot of command database in which every command has to have an isolated context and the functionality of transaction has to be controlled by an extra layer inside the decorator.

推荐答案

问题是由流动/关闭作用域和环境作用域的混合引起的.由于您正在编写WPF应用程序,因此您选择使用Simple Injector的Flowing范围功能.这使您可以直接从作用域解析实例(例如,调用 Scope.GetInstnace ).

The problem is caused by the mixture of both flowing/closure scoping vs ambient scoping. Since you are writing a WPF application, you choose to use Simple Injector's Flowing scopes feature. This allows you to resolve instances directly from a scope (e.g. calling Scope.GetInstnace).

但是,这与Ambient Scoping不兼容,与 AsyncScopedLifestyle.BeginScope 一样.

This, however, doesn't mix with Ambient Scoping, as is what AsyncScopedLifestyle.BeginScope does.

要解决此问题,您必须将装饰器的实现更改为以下内容:

To fix this, you will have to change the implementation of your decorator to the following:

public class TransactionDatabaseCommandDecorator 
    : IDatabaseCommand<DatabaseResult, BusinessCommandParams1>
{
    private readonly Container _container;
    private readonly Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>>
        _decorateeFactory;

    public TransactionDatabaseCommandDecorator(
        Container container,
        Func<IDatabaseCommand<DatabaseResult, BusinessCommandParams1>> decorateeFactory)
    {
        _container = container;
        _decorateeFactory = decorateeFactory;
    }

    public DatabaseResult Execute(BusinessCommandParams1 commandParam)
    {
        DatabaseResult res;
        using (Scope scope = new Scope(_container))
        {
            var command = _decorateeFactory.Invoke(scope);

            var factory = scope
                .GetInstance<IDesignTimeDbContextFactory<WpfRadDispenserDbContext>>();

            ...
        }

        return res;
    }
}

请注意以下有关上述装饰器的内容:

Note the following about the decorator above:

  • 它被注入了 Func< Scope,T> 工厂.该工厂将使用提供的 Scope 创建被装饰者.
  • execute方法现在使用 new Scope(Container)创建一个新的 Scope ,而不是依赖 AsyncScopedLifestyle 的环境范围.
  • li>
  • Func< Scope,T> 工厂随创建的范围一起提供.
  • Scope 实例解析 IDesignTimeDbContextFactory< T> ,而不是使用 Container .
  • It gets injected with a Func<Scope, T> factory. This factory will create the decoratee using the provided Scope.
  • The execute method now creates a new Scope using new Scope(Container) instead of relying on the ambient scoping of AsyncScopedLifestyle.
  • The Func<Scope, T> factory is provided with the created scope.
  • The IDesignTimeDbContextFactory<T> is resolved from the Scope instance, instead of using the Container.

这篇关于使用ScopedLifestyle创建作用域的装饰器.在简单注入器中流动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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