gpt4 book ai didi

c# - 如何在简单注入(inject)器中注册实例集合

转载 作者:行者123 更新时间:2023-12-03 18:49:30 28 4
gpt4 key购买 nike

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

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>();
};
}


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

var accountingProvider = CreateAccountingDbContextProvider(container);
container.RegisterInstance(accountingProvider);
var activeDirectryProvider = CreateAccountingDbContextProvider(container);
container.RegisterInstance(activeDirectryProvider);


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


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);


我尝试使用 container.Collection.Register注册它,没有错误出现,但是我没有在服务层中获取实例,那里得到了空引用异常。

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

最佳答案

您只有一种类型(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基类。尽管基类并不差劲,但是当它们开始获得自己的依赖关系时,它们很可能开始违反单一职责原则,而它们的派生则是依赖关系反转原则和开放/封闭原则:


具有依赖关系的基类通常会成为功能的杂烩,这通常是跨领域的关注点。基础阶级成为不断增长的阶级。不断增长的课程表明违反了单一责任原则。
当基类开始包含逻辑时,派生类将自动依赖于该行为-派生类始终与其基类紧密相关。这使得很难单独测试导数。万一我们想替换或模拟基类的行为,这意味着它的行为是 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);

关于c# - 如何在简单注入(inject)器中注册实例集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58832654/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com