简单的注入器装饰器和协方差

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在没有变化的情况下会非常快,所以从性能的角度来看,我认为没有什么可担心的。