简单的注入器装饰器和协方差
Simple Injector decorator and covariance
我有一个应用程序,其中我的 DbContext
实现有多个连接字符串。为了在消费 类 中支持这一点,我创建了一个带有 Get
方法的 IDbContextProvider
接口,可以为我提供我需要的 DbContext
个实例。
我还有一件 ICommandHandler
事情正在发生,我正在尝试创建一个装饰器,它将在成功执行命令时调用 DbContext.SaveChangesAsync()
。我正在这样注册我的装饰器:
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(SaveChangesCommandHandlerDecorator<>));
现在,因为我不想为 DbContext
实现添加类型参数,而且我知道从 DbContext
派生的所有 类 都有一个 SaveChangesAsync()
方法,我想我可以使用协方差。所以我的界面看起来像这样:
public public interface IDbContextProvider<out TDbContext> where TDbContext : DbContext
{
TDbContext Get(DbPrivileges privileges);
}
以及我的装饰器的相关部分:
public class SaveChangesCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
where TCommand : ICommand
{
private readonly ICommandHandler<TCommand> _handler;
private readonly IDbContextProvider<DbContext> _dbContextProvider;
public SaveChangesCommandHandlerDecorator(
ICommandHandler<TCommand> handler, IDbContextProvider<DbContext> dbContextProvider)
{
_handler = handler;
_dbContextProvider = dbContextProvider;
}
...
然而,当我在我的容器上调用 Verify()
时,它抱怨 IDbContextProvider
无效,因为它正在寻找 "base" IDbContextProvider<DbContext>
而不是其中之一在我的应用程序中注册的那些。
The constructor of type SaveChangesCommandHandlerDecorator<CreateUserCommand> contains the parameter with name 'dbContextProvider' and type IDbContextProvider<DbContext> that is not registered.Please ensure IDbContextProvider<DbContext> is registered, or change the constructor of SaveChangesCommandHandlerDecorator<CreateUserCommand>. Note that there exists a registration for a different type (Redacted).IDbContextProvider<TDbContext> while the requested type is (Redacted).IDbContextProvider<Microsoft.EntityFrameworkCore.DbContext>.
这实际上是有道理的,因为 Simple Injector 无法知道要将什么具体类型注入到 dbContextProvider
参数中。
有什么方法可以自定义我的装饰器的创建方式,以便它可以查看底层 ICommandHandler
实现依赖项的依赖项,并在创建时从那里选择 IDbContextProvider
签名?因此,如果我的命令处理程序有一个 IDbContextProvider<AwesomeDbContext>
,我希望我的装饰器也能解决这个问题。
让我直截了当地说:您的应用程序包含多个 DbContext
实现,例如:
- AwesomeDbContext
- EventBetterDbContext
- BrilliantDbContext
现在每个命令处理程序通常都依赖于一个特定的 DbContext
实现,通过获取 IDbContextProvider<TDbContext>
其中 TDbContext
是特定的 DbContext
实现。
现在,根据装饰器命令处理程序所依赖的内容,您也希望在装饰器中注入完全相同的 IDbContextProvider<TDbContext>
实现,以便您可以保存该特定实例的更改。
如果我正确地总结了你的问题,那么对你问题的简短回答是:不,你不能那样做。
更长的答案是,是的,这实际上可以通过为 TDbContext
添加一个额外的通用类型参数到您的 SaveChangesCommandHandlerDecorator<TCommand, TDbContext>
装饰器,并使用接受 RegisterDecorator
重载 Func<DecoratorPredicateContext, Type>
。使用向工厂委托提供的 DecoratorPredicateContext
,您可以分析它的 Expression
属性 以找出注入了哪个 IDbContextProvider<TDbContext>
,并根据该信息构建一个部分关闭的SaveChangesCommandHandlerDecorator<TCommand, TDbContext>
的版本,您可以在其中填写 TDbContext
,但保留 TCommand
供 Simple Injector 填写。
但老实说,我不会走这条路。这需要大量工作,导致代码难以理解,你的同事会因此讨厌你。
相反,尝试将 IDbContextProvider<TDbContext>
个实例列表注入装饰器。您可以通过单独进行 IDbContextProvider<out TDbContext>
注册以及使用 RegisterCollection
.
作为集合的一部分来做到这一点
当您注入集合时,装饰器可以简单地在所有 DbContext
个实例上调用保存更改。 SaveChanges
在没有变化的情况下会非常快,所以从性能的角度来看,我认为没有什么可担心的。
我有一个应用程序,其中我的 DbContext
实现有多个连接字符串。为了在消费 类 中支持这一点,我创建了一个带有 Get
方法的 IDbContextProvider
接口,可以为我提供我需要的 DbContext
个实例。
我还有一件 ICommandHandler
事情正在发生,我正在尝试创建一个装饰器,它将在成功执行命令时调用 DbContext.SaveChangesAsync()
。我正在这样注册我的装饰器:
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(SaveChangesCommandHandlerDecorator<>));
现在,因为我不想为 DbContext
实现添加类型参数,而且我知道从 DbContext
派生的所有 类 都有一个 SaveChangesAsync()
方法,我想我可以使用协方差。所以我的界面看起来像这样:
public public interface IDbContextProvider<out TDbContext> where TDbContext : DbContext
{
TDbContext Get(DbPrivileges privileges);
}
以及我的装饰器的相关部分:
public class SaveChangesCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
where TCommand : ICommand
{
private readonly ICommandHandler<TCommand> _handler;
private readonly IDbContextProvider<DbContext> _dbContextProvider;
public SaveChangesCommandHandlerDecorator(
ICommandHandler<TCommand> handler, IDbContextProvider<DbContext> dbContextProvider)
{
_handler = handler;
_dbContextProvider = dbContextProvider;
}
...
然而,当我在我的容器上调用 Verify()
时,它抱怨 IDbContextProvider
无效,因为它正在寻找 "base" IDbContextProvider<DbContext>
而不是其中之一在我的应用程序中注册的那些。
The constructor of type SaveChangesCommandHandlerDecorator<CreateUserCommand> contains the parameter with name 'dbContextProvider' and type IDbContextProvider<DbContext> that is not registered.Please ensure IDbContextProvider<DbContext> is registered, or change the constructor of SaveChangesCommandHandlerDecorator<CreateUserCommand>. Note that there exists a registration for a different type (Redacted).IDbContextProvider<TDbContext> while the requested type is (Redacted).IDbContextProvider<Microsoft.EntityFrameworkCore.DbContext>.
这实际上是有道理的,因为 Simple Injector 无法知道要将什么具体类型注入到 dbContextProvider
参数中。
有什么方法可以自定义我的装饰器的创建方式,以便它可以查看底层 ICommandHandler
实现依赖项的依赖项,并在创建时从那里选择 IDbContextProvider
签名?因此,如果我的命令处理程序有一个 IDbContextProvider<AwesomeDbContext>
,我希望我的装饰器也能解决这个问题。
让我直截了当地说:您的应用程序包含多个 DbContext
实现,例如:
- AwesomeDbContext
- EventBetterDbContext
- BrilliantDbContext
现在每个命令处理程序通常都依赖于一个特定的 DbContext
实现,通过获取 IDbContextProvider<TDbContext>
其中 TDbContext
是特定的 DbContext
实现。
现在,根据装饰器命令处理程序所依赖的内容,您也希望在装饰器中注入完全相同的 IDbContextProvider<TDbContext>
实现,以便您可以保存该特定实例的更改。
如果我正确地总结了你的问题,那么对你问题的简短回答是:不,你不能那样做。
更长的答案是,是的,这实际上可以通过为 TDbContext
添加一个额外的通用类型参数到您的 SaveChangesCommandHandlerDecorator<TCommand, TDbContext>
装饰器,并使用接受 RegisterDecorator
重载 Func<DecoratorPredicateContext, Type>
。使用向工厂委托提供的 DecoratorPredicateContext
,您可以分析它的 Expression
属性 以找出注入了哪个 IDbContextProvider<TDbContext>
,并根据该信息构建一个部分关闭的SaveChangesCommandHandlerDecorator<TCommand, TDbContext>
的版本,您可以在其中填写 TDbContext
,但保留 TCommand
供 Simple Injector 填写。
但老实说,我不会走这条路。这需要大量工作,导致代码难以理解,你的同事会因此讨厌你。
相反,尝试将 IDbContextProvider<TDbContext>
个实例列表注入装饰器。您可以通过单独进行 IDbContextProvider<out TDbContext>
注册以及使用 RegisterCollection
.
当您注入集合时,装饰器可以简单地在所有 DbContext
个实例上调用保存更改。 SaveChanges
在没有变化的情况下会非常快,所以从性能的角度来看,我认为没有什么可担心的。