用于使用ScopedLifestyle创建范围的装饰器

问题描述

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

我通过使用this example来实现此实现。

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

components:
  parameters:
    id:
      int:
        in: path
        name: id
        schema:
          type: integer
        required: true
        description: "id"
        example: 1
      uuid:
        in: path
        name: id
        schema:
          type: string
        required: true
        description: "uuid"
        example: "b5fd728d-1c78-4085-89ba-579574b5831f"

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

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

为此,我已经实现

事务装饰器:

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

实施示例:

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()); 的实例,但是当我尝试从工厂获取实例时,我会收到此错误

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

TransactionDatabaseCommandDecorator

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

基本上我想拥有类似命令修饰器的东西

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

但由DI和Simple Injector制成

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

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

解决方法

问题是由流动/关闭范围和环境范围的混合引起的。由于您正在编写WPF应用程序,因此您选择使用Simple Injector的Flowing范围功能。这样,您就可以直接从作用域解析实例(例如,调用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方法现在使用Scope创建一个新的new Scope(Container),而不是依赖于AsyncScopedLifestyle的环境范围。
  • Func<Scope,T>工厂随创建的作用域一起提供。
  • IDesignTimeDbContextFactory<T>是通过Scope实例解析的,而不是使用Container

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...