在简单注入器中使用 ScopedLifestyle.Flowing 创建作用域的装饰器

Decorator for creating Scope with ScopedLifestyle.Flowing in Simple Injector

我需要一些帮助来了解我的容器配置有什么问题。

我基于此实现使用 this example

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

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

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

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

为此我已经实现了

事务装饰器:

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 的实例,但是当我尝试从工厂获取实例时,我收到了这个错误

SimpleInjector.ActivationException: WpfRadDispenserUOW is registered using the 'Scoped' lifestyle, but the instance is requested outside the context of an active (Scoped) scope. Please see https://simpleinjector.org/scoped for more information about how apply lifestyles and manage scopes.

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。 但是构造函数注入是由容器处理的,所以我如何在命令中注入装饰器创建的上下文?

基本上我想要由命令的装饰者制作的东西

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 和简单注射器制造

也许我的设计存在一些问题或几个问题,但我是 DI 的新手,我想更好地了解它是如何工作的。

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

问题是由 flowing/closure 作用域与环境作用域混合引起的。由于您正在编写 WPF 应用程序,因此您选择使用 Simple Injector 的 Flowing scopes 功能。这允许您直接从范围解析实例(例如调用 Scope.GetInstnace)。

然而,这并不像 AsyncScopedLifestyle.BeginScope 那样与环境作用域混合。

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

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;
    }
}

注意上面装饰器的以下几点:

  • 它被注入了一个 Func<Scope, T> 工厂。该工厂将使用提供的 Scope.
  • 创建装饰对象
  • execute 方法现在使用 new Scope(Container) 创建一个新的 Scope 而不是依赖 AsyncScopedLifestyle.
  • 的环境范围
  • Func<Scope, T> 工厂提供了创建的作用域。
  • IDesignTimeDbContextFactory<T> 是从 Scope 实例解析的,而不是使用 Container