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

时间:2017-11-13 21:32:20

标签: c# dependency-injection ioc-container simple-injector

我有一个应用程序,我的DbContext实现有多个连接字符串。为了在消费类中支持这一点,我创建了一个IDbContextProvider接口,其中Get方法可以为我提供我需要的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无效,因为它正在寻找“基础”IDbContextProvider<DbContext>而不是其中一个注册的我的应用。

  

SaveChangesCommandHandlerDecorator类型的构造函数&lt; CreateUserCommand&gt;包含名称为“dbContextProvider”的参数,并键入IDbContextProvider&lt; DbContext&gt;那是没有注册的。请确保IDbContextProvider&lt; DbContext&gt;已注册,或更改SaveChangesCommandHandlerDecorator&lt; CreateUserCommand&gt;的构造函数。请注意,存在不同类型的注册(编辑).IDbContextProvider&lt; TDbContext&gt;请求的类型是(编辑).IDbContextProvider&lt; Microsoft.EntityFrameworkCore.DbContext&gt;。

这实际上是有道理的,因为Simple Injector无法知道注入dbContextProvider参数的具体类型。

有没有办法自定义我的装饰器的创建方式,以便它可以查看底层ICommandHandler实现的依赖关系的依赖关系,并在创建时从那里选择IDbContextProvider签名?因此,如果我的命令处理程序有一个IDbContextProvider<AwesomeDbContext>,我也希望为我的装饰器解析它。

1 个答案:

答案 0 :(得分:2)

让我直截了当地说明:您的应用程序包含多个DbContext实现,例如:

  • AwesomeDbContext
  • EventBetterDbContext
  • BrilliantDbContext

现在,每个命令处理程序通常依赖于一个特定的DbContext实现,方法是获取IDbContextProvider<TDbContext> TDbContext是特定DbContext实现的位置。

现在取决于装饰器命令处理程序所依赖的内容,您希望在装饰器中注入完全相同的IDbContextProvider<TDbContext>实现,这样您就可以保存该特定实例的更改。

如果我正确地总结了你的问题,你问题的简短回答是:不,你不能这样做。

答案越长,是的,实际上可以通过向TDbContext装饰器添加SaveChangesCommandHandlerDecorator<TCommand, TDbContext>的额外泛型类型参数来实现,并使用接受{{1}的RegisterDecorator重载}}。使用提供的Func<DecoratorPredicateContext, Type>工厂委托,您可以分析其DecoratorPredicateContext属性以找出注入的Expression,并根据该信息构建部分关闭的{{}}版本1}}您在其中填写IDbContextProvider<TDbContext>,但保留SaveChangesCommandHandlerDecorator<TCommand, TDbContext>以便Simple Injector填写。

但说实话,我不会采取这条路线。这需要大量的工作,导致难以理解的代码,而你的同事会因此而讨厌你。

相反,请尝试将TDbContext个实例列表注入装饰器中。您可以单独进行TCommand注册,也可以使用IDbContextProvider<TDbContext>作为集合的一部分来完成此操作。

当您注入集合时,装饰器可以简单地在所有IDbContextProvider<out TDbContext>实例上调用保存更改。如果没有变化,RegisterCollection会非常快,所以从表现的角度来看,我认为没有什么可担心的。