【问题标题】:Resolve more than one object with the same class and interface with Simple Injector使用 Simple Injector 解析多个具有相同类和接口的对象
【发布时间】:2013-06-04 16:52:00
【问题描述】:

我正在尝试在我的新项目中从 Unity 迁移到 Simple Injector。它比 Unity 快得多,我不得不试一试。我有一些颠簸,但没有什么是我无法解决的。但是我用“按键查找”打了另一个

我已阅读this,其中简单注入器的创建者表示他认为您不需要为每个接口解析多个类。

我一定是个差劲的程序员,因为我已经用 Unity(它很好地支持它)做到了这一点,并且想在我当前的项目中做到这一点。

我的情况是我有一个 IRepository 接口。我有两个单独的存储库,我想使用 IRepository 接口进行抽象。像这样:

container.Register<FirstData>(() => new FirstData());
container.Register<IRepository>(
           () => new GenericRepository(container.GetInstance<FirstData>()));


container.Register<SecondEntities>(() => new SecondEntities());
container.Register<IRepository>(
           () => new GenericRepository(container.GetInstance<SecondData>()));

IRepository/GenericRepository 是一种相当常见的抽象,但在 SimpleInjector 中只能有一个

在 Unity 中,我可以注册我的两个存储库,然后设置我的构造函数注入来注入我需要的实例。这是使用实例的密钥完成的。 (我不需要在我的正常代码中调用Resolve,也不需要在我的设置之外添加对 Unity 的依赖。)

使用简单的注射器,这是行不通的。但是,无论好坏,Simple Injector 的所有者都认为这个功能是个坏主意。

注意:作者的“应用内”系统看起来使用字符串键进行查找,但每次仍需要不同的类(DefaultRequestHandlerOrdersRequestHandlerCustomersRequestHandler)。 我只有一个 GenericRepostory,它允许我抽象我的存储库方法,无论我连接到什么。

我想我可以在每次想要实例化它时继承我的GenericRepostory。或者让它采用我不需要的随机类型参数。但这混淆了我的设计,所以我希望有另一种方法来做到这一点。

那么有没有什么变通办法不让我创建虚假类型来区分我的两个 IRepository/GenericRepository 实例?

【问题讨论】:

  • 你能展示依赖于 IRepository 抽象的类或注册吗?我不清楚你想如何使用这些存储库。

标签: c# .net simple-injector


【解决方案1】:

我们最终将通用存储库更改为如下所示:

/// The Type parameter has no funcionality within the repository, 
/// it is only there to help us differentiate when registering 
/// and resolving different repositories with Simple Injector.
public class GenericRepository<TDummyTypeForSimpleInjector> : IRepository

(我们为其添加了类型参数)。
然后我们创建了两个这样的虚拟类(我更改了类的名称以匹配我的示例):

// These are just dummy classes that are used to help 
// register and resolve GenericRepositories with Simple Injector.
public class FirstDataSelector { }
public class SecondDataSelector { }

然后我可以像这样注册它们:

container.Register<FirstData>(() => new FirstData());
container.Register(() => new GenericRepository<FirstDataSelector>
                   (container.GetInstance<FirstData>()));


container.Register<SecondEntities>(() => new SecondEntities());
container.Register(() => new GenericRepository<SecondDataSelector>
                   (container.GetInstance<SecondData>()));

(请注意 GenericRepository 上的泛型类型参数,并且我没有将其注册为 IRepository。这两项更改对于完成这项工作至关重要。)

这很好用。然后我就可以在我的业务逻辑的构造函数注入中使用该注册。

container.Register<IFirstBusiness>(() => new FirstBusiness
               (container.GetInstance<GenericRepository<FirstDataSelector>>()));
container.Register<ISecondBusiness>(() => new SecondBusiness
               (container.GetInstance<GenericRepository<SecondDataSelector>>()));

由于我的业务类采用IRepository,它可以正常工作,并且不会将 IOC 容器或我的存储库的实现暴露给业务类。

我基本上使用 Type 参数作为 Key 进行查找。 (我知道一个 hack,但我的选择有限。)

不得不在我的设计中添加虚拟类有点令人失望,但我们的团队认为这个缺点是值得的,而不是放弃 Simple Injector 并回到 Unity。

【讨论】:

    【解决方案2】:

    您自己的答案实际上非常好,但不幸的是您将泛型类型参数视为一个虚拟参数;你应该让它成为你设计的一等公民:

    public interface IRepository<TData> { }
    
    public clss GenericRepository<TData> : IRepository<TData>
    { 
        public GenericRepository(TData data) { }
    }
    

    这样你就可以简单地注册他们如下:

    container.Register<IRepository<FirstData>, GenericRepository<FirstData>>();
    container.Register<IRepository<SecondData>, GenericRepository<SecondData>>();
    

    在这种情况下,您的业务类可以简单地依赖于通用 IRepository&lt;FirstData&gt;IRepository&lt;SecondData&gt;,并且可以简单地注册如下:

    container.Register<IFirstBusiness, FirstBusiness>();
    container.Register<ISecondBusiness, SecondBusiness>();
    

    请注意此处给出的注册如何不使用任何 lambda。 Simple Injector 可以为您找到这一点。这使您的 DI 配置更简单、更易读,尤其是:更易于维护。

    通过这种方式,您可以使您的设计非常明确和明确。您的设计不明确,因为您有一个(非通用)IRepository 接口,应该映射到多个实现。虽然这不一定在所有情况下都是坏事,但在大多数情况下,这种歧义可以而且应该被防止,因为这会使您的代码和配置变得复杂。

    此外,由于您的通用 GenericRepository&lt;T&gt; 现在映射到通用 IRepository&lt;T&gt; 我们可以用一行替换所有 Register&lt;IRepository&lt;T&gt;, GenericRepository&lt;T&gt;&gt;() 注册:

    // using SimpleInjector.Extensions;
    container.RegisterOpenGeneric(typeof(IRepository<>),
        typeof(GenericRepository<>);
    

    更进一步,您的业务类也可以从通用类型中受益。以this article 为例,其中每个业务操作都有自己的类,但所有业务操作都隐藏在相同的通用ICommandHandler&lt;TCommand&gt; 抽象后面。当您这样做时,所有业务类都可以通过一次调用注册:

    container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
        typeof(ICommandHandler<>).Assembly);
    

    此调用在提供的程序集中搜索ICommandHandler&lt;TCommand&gt; 接口的实现,并在容器中注册每个找到的实现。您可以添加新的业务逻辑(用例),而无需更改配置。但这只是对业务逻辑进行这种抽象的众多优势之一。另一个很大的优势是它使添加横切关注点(例如日志记录、事务处理、安全性、审计跟踪、缓存等)更容易实现。

    【讨论】:

    • 关于这个问题,我想问一个后续问题。假设我正在使用 NHibernate,并且我有两个独立的 ISessionFactory 实例到不同的数据库。如果我没有命名依赖解析,我必须按照建议将工厂包装在另一个具有虚拟泛型类型的泛型类中。你在这里看到任何其他方式吗?特别是因为 ISessionFactory 作为接口已经被提供并且是非泛型的。
    • @Trustme-I'maDoctor:因为您正在与两个不同的数据库(假设有两个不同的模式)交谈,所以它们应该有自己的抽象:例如 ICmsSessionFactoryIOrderSystemSessionFactory(可能没有在这种情况下需要通用接口)。如果您别无选择,您始终可以使用Context Based Injection(就像 Ninjects When 方法),但大约 99% 的时间您应该解决设计中的歧义;不在您的 DI 配置中。
    • 好点。我只希望通过组合实现接口更容易,所以我可以做例如ICmsSessionFactory : ISessionFactory 通过在实现中包装 NHibernate 的 SessionFactory。好吧,我们会看到:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-01
    • 1970-01-01
    • 2014-11-23
    • 1970-01-01
    • 1970-01-01
    • 2016-06-07
    • 2014-11-04
    相关资源
    最近更新 更多