在简单注入器中使用 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
。
我需要一些帮助来了解我的容器配置有什么问题。
我基于此实现使用 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
。