如何在简单注入器中注册实例集合

在我的项目中,我有多个数据库上下文,因此我创建了一个提供程序,用于根据需要获取上下文对象。 我的提供者看起来像这样。

gameObject.transform.right

我创建了一个public class DbContextProvider { public Func<accountingContext> accountingDbContextResolver { get; set; } public Func<activeDirectryContext> activeDirectryDbContextResolver { get; set; } public accountingContext GetaccountingDbContext() => this.accountingDbContextResolver(); public activeDirectryContext GetactiveDirectryDbContext() => this.activeDirectryDbContextResolver(); } 类来获取提供程序

ServiceBase

我正在使用简单的注入器,并且需要创建两个数据库上下文的实例。

为了获得实例,我用委托创建了两个静态方法

public class ServiceBase
{
    public ServiceBase(DbContextProvider contextProvider)
    {
        this.ContextProvider = contextProvider;
    }

    protected DbContextProvider ContextProvider { get; }

    public accountingContext accountingDbContext =>
        this.ContextProvider.GetaccountingDbContext();

    public activeDirectryContext activeDirectryDbContext =>
        this.ContextProvider.GetactiveDirectryDbContext();
}

对于注册,我使用了以下代码

private static DbContextProvider CreateactiveDirectryDbContextProvider(Container container)
{
    return new DbContextProvider
    {
        activeDirectryDbContextResolver =
            () => container.GetInstance<activeDirectryContext>();
    };
}

private static DbContextProvider CreateaccountingDbContextProvider(Container container)
{
    return new DbContextProvider
    {
        accountingDbContextResolver = () => container.GetInstance<accountingContext>();
    };
}

如果我运行代码,则出现如下错误

  

System.InvalidOperationException:'类型DbContextProvider具有   已经注册。如果您打算解决托收问题   的DbContextProvider实现,请使用   Container.Collection.Register重载。有关更多信息,请参见   https://simpleinjector.org/coll1。如果您打算更换   现有注册与此新注册,您可以允许   通过设置覆盖当前注册   Container.Options.AllowOverridingRegistrations为true。欲了解更多   信息,请参阅https://simpleinjector.org/ovrrd。”

但是当我尝试仅使用一种上下文

时,一切工作正常
var accountingProvider = CreateaccountingDbContextProvider(container);
container.RegisterInstance(accountingProvider);
var activeDirectryProvider = CreateaccountingDbContextProvider(container);
container.RegisterInstance(activeDirectryProvider);

我尝试使用var accountingProvider = CreateaccountingDbContextProvider(container); container.RegisterInstance(accountingProvider); 注册它,没有错误出现,但是我没有在服务层中获取实例,那里得到了空引用异常。

有人可以帮我解决这个问题吗?

sqli182 回答:如何在简单注入器中注册实例集合

您只有一种类型(DbContextProvider)负责构造AccountingContextActiveDirectryContext。从这个角度来看,创建两个分别部分初始化的DbContextProvider实例真的很奇怪。消费者不会期望GetAccountingDbContext()返回null或抛出NullReferenceException。因此,您应该创建一个可以在两种情况下使用的单个实例:

container.Register<ActiveDirectryContext>(Lifestyle.Scoped);
container.Register<AccountingContext>(Lifestyle.Scoped);

container.RegisterInstance<DbContextProvider>(new DbContextProvider
{
    ActiveDirectryDbContextResolver = () => container.GetInstance<ActiveDirectryContext>(),AccountingDbContextResolver = () => container.GetInstance<AccountingContext>()
});

或者更好,使DbContextProvider不可变:

container.RegisterInstance<DbContextProvider>(new DbContextProvider(
    activeDirectryDbContextResolver: () => container.GetInstance<ActiveDirectryContext>(),accountingDbContextResolver: () => container.GetInstance<AccountingContext>()));

这解决了此问题,因为DbContextProvider不仅只有一个注册。这样可以消除歧义,防止可能的错误,并且是一种更简单的解决方案。

但是,尽管这可行,但我还是建议您对设计进行一些更改。

基于继承的构成

首先,您应该摆脱ServiceBase基类。尽管基类并不差劲,但是当它们开始获得自己的依赖关系时,它们很可能开始违反单一责任原则,而它们的派生则是违反了 Dependency Inversion Principle 以及开放/封闭原则

  • 具有依赖关系的基类通常成为功能的大杂烩,通常是跨领域的关注点。基础阶级成为一个不断增长的阶级。不断增长的课程表明违反了单一责任原则。
  • 当基类开始包含逻辑时,派生类自动取决于该行为-派生类总是始终与其基类紧密耦合。这使得很难单独测试导数。万一我们想替换或模拟基类的行为,这意味着它的行为是Volatile。当类与可变依赖紧密结合时,这意味着您违反了依赖反转原则。
  • 基类的构造函数依赖关系需要由派生类的构造函数提供。当基类需要新的依赖项时,这将引起彻底的更改,因为所有派生的构造函数也需要更新。

代替使用基类,请执行以下操作:

  • 派生类应该将依赖项本身存储在私有字段中,而不是将依赖项从派生类转发到基类构造函数。它可以直接使用该依赖项。
  • 如果基类除代码外还包含其他行为:
    • 如果行为是易变的,则将逻辑包装在一个类中,将该类隐藏在抽象之后,然后将该类(通过其抽象)注入派生类的构造函数中。这样做时,很容易查看派生类具有哪些依赖项。
    • 如果行为是稳定的,则可以使用静态帮助器类或扩展方法使基类的行为可重用。
    • 如果行为涉及跨领域的关注,请考虑使用Decorators或Dynamic Interception作为基本类的替代方法。

当您遵循此建议时,最终得到的是一组(派生的)服务类,这些类依赖于基类,而基类不过是一个空的shell。这是您可以完全删除基类的时候。您现在实现的是Composition over Inheritance。与继承相比,组合通常导致可维护性更高的系统。

关闭构成模型

如JoostK所述,您还可以将DbContext类型直接注入使用者。但是,是否要执行此操作取决于您决定使用的type of composition model。这意味着,当您选择应用Closure Composition Model时,通常应将DbContext实现直接注入到您的使用者中。

public class ProduceIncomeTaxService : IHandler<ProduceIncomeTax>
{
    private readonly AccountingContext context;

    // Inject stateful AccountingContext directly into constructor
    public ProduceIncomeTaxService(AccountingContext context) => this.context = context;

    public void Handle(ProduceIncomeTax command)
    {
        var record = this.context.AccountingRecords
            .Single(r => r.Id == command.Id);

        var tax = CalculateIncomeTax(record);

        FaxIncomeTax(tax);

        this.context.SaveChanges();
    }

    ...
}

这简化了系统的注册,因为现在您只需注册DbContext实现,就可以完成:

container.Register<ActiveDirectryContext>(Lifestyle.Scoped);
container.Register<AccountingContext>(Lifestyle.Scoped);

// Of course don't forget to register your service classes.

界面隔离原理

您当前的DbContextProvider似乎是围绕Ambient Composition Model设计的。两种合成模型都有advantages,您可能已经为环境合成模型故意选择了。

不过,DbContextProvider仍然具有许多(10)属性-每个DbContext都有一个。具有许多方法的类和抽象可能会引起许多与可维护性有关的问题。这源于规定了狭窄抽象的接口隔离原则。因此,与其注入一个允许访问单个DbContext类型的广泛的提供程序实现,也不是。实现通常只需要访问单个DbContext类型。如果他们需要多个班级,那么几乎应该将班级划分为更小,更集中的班级。

因此,您可以做的是创建一个通用抽象,该抽象允许访问单个DbContext类型:

public interface IDbContextProvider<T> where T : DbContext
{
    T Context { get; }
}

在消费者中使用时,其外观如下:

public class ProduceIncomeTaxService : IHandler<ProduceIncomeTax>
{
    private readonly IDbContextProvider<AccountingContext> provider;

    // Inject an ambient context provider into the constructor
    public ProduceIncomeTaxService(IDbContextProvider<AccountingContext> provider)
        => this.provider = provider;

    public void Handle(ProduceIncomeTax command)
    {
        var record = this.provider.Context.AccountingRecords
            .Single(r => r.Id == command.Id);

        var tax = CalculateIncomeTax(record);

        FaxIncomeTax(tax);

        this.provider.Context.SaveChanges();
    }

    ...
}

有多种实现IDbContextProvider<T>的方法,但是,例如,您可以创建直接依赖于Simple Injector的实现:

public sealed class SimpleInjectorDbContextProvider<T> : IDbContextProvider<T>
    where T : DbContext
{
    private readonly InstanceProducer producer;

    public SimpleInjectorDbContextProvider(Container container)
    {
        this.producer = container.GetCurrentRegistrations()
            .FirstOrDefault(r => r.ServiceType == typeof(T))
            ?? throw new InvalidOperationException(
                $"You forgot to register {typeof(T).Name}. Please call: " +
                $"container.Register<{typeof(T).Name}>(Lifestyle.Scope);");
    }

    public T Context => (T)this.producer.GetInstance();
}

此类使用注入的Container来为给定的InstanceProducer类型提取正确的DbContext注册。如果未注册,则会引发异常。然后在调用InstanceProducer时使用DbContext来获取Context

由于此类取决于Simple Injector,因此它应该是您的Composition Root的一部分。

您可以按以下方式注册它:

container.Register<ActiveDirectryContext>(Lifestyle.Scoped);
container.Register<AccountingContext>(Lifestyle.Scoped);

container.Register(
    typeof(IDbContextProvider<>),typeof(SimpleInjectorDbContextProvider<>),Lifestyle.Singleton);
本文链接:https://www.f2er.com/3111931.html

大家都在问