具有使用相同接口(StructureMap 或任何其他 DI 框架)的多个数据库的 IOC

发布于 2024-08-11 04:43:52 字数 1542 浏览 7 评论 0原文

我们一直在尝试 StructureMap,但我很难掌握如何处理单个接口有多个实现的情况。下面的代码显示了一个示例,其中我们有两个数据库,都可以从单个服务访问。

public class SomeController : Controller
{
    private ISomeService _service;
    private IClientRepository _repository;
    protected IContext _masterContext;
    protected IContext _clientContext;

    public SomeController(ISomeService service, ISomeRepository repository
        , IContext masterCon, IContext clientCon)
    {
        _service = service;
        _repository = repository;
        _masterContext = masterCon;
        _clientContext = clientCon;
    }
}

public class SomeService : ISomeService
{
    private IContext _masterContext;
    private IContext _clientContext;

    public SomeService(IContext masterContext, IContext clientContext)
    {
        masterContext = _masterContext;
        clientContext = _clientContext;
    }
}

public class ClientRepository : IClientRepository
{
    private IContext _clientContext;

    public ClientRepository(IContext clientContext)
    {
        _clientContext = clientContext;
    }
}

public class MasterContext : IContext
{
    public MasterContext(String connString)
    //<snip, snip> implement 3rd party data context
}

public class ClientContext : IContext
{
    public ClientContext(String connString)
    //<snip, snip> implement 3rd party data context
}

当我们只有一个上下文(数据库)时,StructureMap 工作得很好,但是我如何告诉它如何解析第二个呢?注意:在大多数情况下,我们不会有一个服务处理 2 个数据库(但可能有一个控制器处理 2 个连接,即 2 个存储库访问 2 个不同的数据库),但它似乎仍然没有让事情变得更容易。

我已经准备好放弃使用 IoC 框架并回到穷人的 DI 了。

We've been experimenting with StructureMap, and I'm having trouble grasping how to handle situations where a single interface has multiple implementations. The code below shows an example where we have two databases that are both accessible from a single service.

public class SomeController : Controller
{
    private ISomeService _service;
    private IClientRepository _repository;
    protected IContext _masterContext;
    protected IContext _clientContext;

    public SomeController(ISomeService service, ISomeRepository repository
        , IContext masterCon, IContext clientCon)
    {
        _service = service;
        _repository = repository;
        _masterContext = masterCon;
        _clientContext = clientCon;
    }
}

public class SomeService : ISomeService
{
    private IContext _masterContext;
    private IContext _clientContext;

    public SomeService(IContext masterContext, IContext clientContext)
    {
        masterContext = _masterContext;
        clientContext = _clientContext;
    }
}

public class ClientRepository : IClientRepository
{
    private IContext _clientContext;

    public ClientRepository(IContext clientContext)
    {
        _clientContext = clientContext;
    }
}

public class MasterContext : IContext
{
    public MasterContext(String connString)
    //<snip, snip> implement 3rd party data context
}

public class ClientContext : IContext
{
    public ClientContext(String connString)
    //<snip, snip> implement 3rd party data context
}

StructureMap worked GREAT when we had a single context (database), but how do I tell it how to resolve the 2nd? Note: in most situations we wouldn't have a service handling 2 databases (but may have a controller handling 2 connections, i.e. 2 repositories accessing 2 different databases), but it still doesn't seem to make it easier.

I'm half ready to just give up on using an IoC framework and go back to poor man's DI.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

秉烛思 2024-08-18 04:43:52

是否不可能有一个 IClientContext 和一个 IMasterContext,可能继承自 IContext。我的感觉是,代码将执行两种截然不同的操作之一,具体取决于您是与“主”数据库还是“客户端”数据库通信。

Is it not possible to have an IClientContext and an IMasterContext, possibly inheriting from IContext. My feeling is that the code would be doing one of two very different things depending on whether you were talking to the 'Master' or 'Client' database.

み零 2024-08-18 04:43:52

Unity 中,您可以进行命名注册,从而允许您为给定接口有效注册多个类。所以你可以这样做(用心打字,如果有兴趣,请检查实际的 Unity 文档):

container.RegisterType<IContext, MasterContext>("Master");
container.RegisterType<IContext, ClientContext>("Client");

然后 SomeService 的构造函数将是:

public SomeService(
    [Dependency("Master")]IContext masterContext, 
    [Dependency("Client")]IContext clientContext)
{
    //...
}

缺点是,通过这种方式,你的服务类不再独立于所使用的 DI 框架,而是依赖于可能还可以的项目。

In Unity you can have named registrations, allowing you to effectively register more than a class for a given interface. So you could do (typing by heart, check the actual Unity documentation if interested):

container.RegisterType<IContext, MasterContext>("Master");
container.RegisterType<IContext, ClientContext>("Client");

and then the constructor for SomeService would be:

public SomeService(
    [Dependency("Master")]IContext masterContext, 
    [Dependency("Client")]IContext clientContext)
{
    //...
}

The drawback is that in this way your service class is no longer independent of the DI framework used, but depending on the project that may be ok.

紫﹏色ふ单纯 2024-08-18 04:43:52

如果您依赖 StructureMap 自动解决依赖关系,这可能会有点困难。第一个解决方案(也是我会犯的错误)是利用像理查德在他的答案中提到的标记接口,然后注册它们。然后,您可以明确指定是否需要客户端上下文或主上下文。

第二种方法是使用命名注册,然后显式指定构造函数参数。

ForRequestedType<IContext>().AddInstances(
    i => {
        i.OfConcreteType<ClientContext>().WithName("Client");
        i.OfConcreteType<MasterContext>().WithName("Master");
    });
ForRequestedType<SomeController>().TheDefault.Is.ConstructedBy(
    i => new SomeController(i.GetInstance<ISomeService>(), 
        i.GetInstance<IClientRepository>(), 
        i.GetInstance<IContext>("Master"), 
        i.GetInstance<IContext>("Client")));

不是特别好,但它可以完成工作,最终如果它只在一两个地方,它可能还可以。


如果您想对命名空间/程序集进行不同的解析,您可以尝试如下操作:-

ForRequestedType<IContext>().AddInstances(
    i => {
         i.OfConcreteType<ClientContext>().WithName("Client");
         i.OfConcreteType<MasterContext>().WithName("Master");
     }).TheDefault.Is.Conditional(c => {
         c.If(con => con.ParentType.Namespace.EndsWith("Client"))
          .ThenIt.Is.TheInstanceNamed("Client");
         c.If(con => con.ParentType.Namespace.EndsWith("Master"))
          .ThenIt.Is.TheInstanceNamed("Master");
         c.TheDefault.Is.OfConcreteType<ClientContext>();
     });

ParentType 上的谓词可以引用程序集(或您真正想要的任何内容)

This can be a little difficult if you're relying on StructureMap to resolve the dependencies automatically. The first solution (and what I'd err towards) is to make use of marker interfaces like Richard mentions in his answer then just register them. You can then explicitly specify whether you want your client or master context there.

The second way is to make use of named registrations, then specify the constructor params explicitly.

ForRequestedType<IContext>().AddInstances(
    i => {
        i.OfConcreteType<ClientContext>().WithName("Client");
        i.OfConcreteType<MasterContext>().WithName("Master");
    });
ForRequestedType<SomeController>().TheDefault.Is.ConstructedBy(
    i => new SomeController(i.GetInstance<ISomeService>(), 
        i.GetInstance<IClientRepository>(), 
        i.GetInstance<IContext>("Master"), 
        i.GetInstance<IContext>("Client")));

Not particularly nice but it does the job and ultimately if it's only in one or two places it might be OK.


If you want to resolve differently on namespace / assembly you could try something like this:-

ForRequestedType<IContext>().AddInstances(
    i => {
         i.OfConcreteType<ClientContext>().WithName("Client");
         i.OfConcreteType<MasterContext>().WithName("Master");
     }).TheDefault.Is.Conditional(c => {
         c.If(con => con.ParentType.Namespace.EndsWith("Client"))
          .ThenIt.Is.TheInstanceNamed("Client");
         c.If(con => con.ParentType.Namespace.EndsWith("Master"))
          .ThenIt.Is.TheInstanceNamed("Master");
         c.TheDefault.Is.OfConcreteType<ClientContext>();
     });

Where the predicate on ParentType can refer to Assembly (or whatever you want really)

撧情箌佬 2024-08-18 04:43:52

如果有人遇到这个问题,您可以使用工厂模式来实现。

服务扩展

public static class ServiceFactoryExtensions
{
    public static void RegisterSqlFactory(this IServiceCollection serviceCollection)
    {
        serviceCollection.Configure<MsSqlOption>(option => option.ConnectionString = "Mssql connection string");
        serviceCollection.Configure<MySqlOption>(option => option.ConnectionString = "Mysql connection string");
        serviceCollection.Configure<PostgreOption>(option => option.ConnectionString = "Postgrel connection string");

        serviceCollection.AddSingleton<ISqlDatabase, MsSql>();
        serviceCollection.AddSingleton<ISqlDatabase, Postgre>();
        serviceCollection.AddSingleton<ISqlDatabase, MySql>();
        serviceCollection.AddSingleton<Func<IEnumerable<ISqlDatabase>>>(serviceProvider => () => serviceProvider.GetService<IEnumerable<ISqlDatabase>>());
        serviceCollection.AddSingleton<ISqlDatabaseFactory, SqlDatabaseFactory>();
    }
}

工厂类

public class SqlDatabaseFactory : ISqlDatabaseFactory
{
    private readonly Func<IEnumerable<ISqlDatabase>> _factory;

    public SqlDatabaseFactory(Func<IEnumerable<ISqlDatabase>> factory)
    {
        _factory = factory;
    }

    public ISqlDatabase CreateSql(SqlType sqlType)
    {
        var databases = _factory();
        var sqlDatabase = databases.FirstOrDefault(x => x.DatabaseName == sqlType);

        if (sqlDatabase == null)
            throw new NotImplementedException($"Sql type {nameof(sqlType)} is not implemented");

        return sqlDatabase;
    }
}

Sql类

public class MsSql : ISqlDatabase
{
    public SqlType DatabaseName => SqlType.MsSql;

    public string Connecionstring { get; private set; }

    public MsSql(IOptions<MsSqlOption> option)
    {
        Connecionstring = option.Value.ConnectionString;
    }
}

public class Postgre : ISqlDatabase
{
    public SqlType DatabaseName => SqlType.Postgre;

    public string Connecionstring { get; private set; }

    public Postgre(IOptions<PostgreOption> option)
    {
        Connecionstring = option.Value.ConnectionString;
    }        
}

public class MySql : ISqlDatabase
{
    public SqlType DatabaseName => SqlType.MySql;

    public string Connecionstring { get; private set; }

    public MySql(IOptions<MySqlOption> option)
    {
        Connecionstring = option.Value.ConnectionString;
    }
}

public interface ISqlDatabase
{
    string Connecionstring { get; }

    SqlType DatabaseName { get; }
}

public enum SqlType
{
    MsSql,
    Postgre,
    MySql
}

用法

internal class Program
{
    static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.RegisterSqlFactory();

        var provider = serviceCollection.BuildServiceProvider();

        var sqlFactory = provider.GetService<ISqlDatabaseFactory>();

        var mySql = sqlFactory.CreateSql(SqlType.MySql);
        var msSql = sqlFactory.CreateSql(SqlType.MsSql);
        var postgre = sqlFactory.CreateSql(SqlType.Postgre);

        Console.WriteLine($"Database Type : {mySql.DatabaseName}, Connectionstring: {mySql.Connecionstring}");
        Console.WriteLine($"Database Type : {msSql.DatabaseName}, Connectionstring: {msSql.Connecionstring}");
        Console.WriteLine($"Database Type : {postgre.DatabaseName}, Connectionstring: {postgre.Connecionstring}");

        Console.ReadKey();
    }
}

输出
输入图片此处描述

依赖项:

  • .Net Core 3.1
  • Microsoft.Extensions.DependencyInjection;
  • Microsoft.Extensions.Options;
  • 系统
  • System.Collections.Generic
  • System.Linq;

In case someone stumble in this problem, you can achieve it using factory pattern.

Service extension

public static class ServiceFactoryExtensions
{
    public static void RegisterSqlFactory(this IServiceCollection serviceCollection)
    {
        serviceCollection.Configure<MsSqlOption>(option => option.ConnectionString = "Mssql connection string");
        serviceCollection.Configure<MySqlOption>(option => option.ConnectionString = "Mysql connection string");
        serviceCollection.Configure<PostgreOption>(option => option.ConnectionString = "Postgrel connection string");

        serviceCollection.AddSingleton<ISqlDatabase, MsSql>();
        serviceCollection.AddSingleton<ISqlDatabase, Postgre>();
        serviceCollection.AddSingleton<ISqlDatabase, MySql>();
        serviceCollection.AddSingleton<Func<IEnumerable<ISqlDatabase>>>(serviceProvider => () => serviceProvider.GetService<IEnumerable<ISqlDatabase>>());
        serviceCollection.AddSingleton<ISqlDatabaseFactory, SqlDatabaseFactory>();
    }
}

Factory class

public class SqlDatabaseFactory : ISqlDatabaseFactory
{
    private readonly Func<IEnumerable<ISqlDatabase>> _factory;

    public SqlDatabaseFactory(Func<IEnumerable<ISqlDatabase>> factory)
    {
        _factory = factory;
    }

    public ISqlDatabase CreateSql(SqlType sqlType)
    {
        var databases = _factory();
        var sqlDatabase = databases.FirstOrDefault(x => x.DatabaseName == sqlType);

        if (sqlDatabase == null)
            throw new NotImplementedException(
quot;Sql type {nameof(sqlType)} is not implemented");

        return sqlDatabase;
    }
}

Sql classes

public class MsSql : ISqlDatabase
{
    public SqlType DatabaseName => SqlType.MsSql;

    public string Connecionstring { get; private set; }

    public MsSql(IOptions<MsSqlOption> option)
    {
        Connecionstring = option.Value.ConnectionString;
    }
}

public class Postgre : ISqlDatabase
{
    public SqlType DatabaseName => SqlType.Postgre;

    public string Connecionstring { get; private set; }

    public Postgre(IOptions<PostgreOption> option)
    {
        Connecionstring = option.Value.ConnectionString;
    }        
}

public class MySql : ISqlDatabase
{
    public SqlType DatabaseName => SqlType.MySql;

    public string Connecionstring { get; private set; }

    public MySql(IOptions<MySqlOption> option)
    {
        Connecionstring = option.Value.ConnectionString;
    }
}

public interface ISqlDatabase
{
    string Connecionstring { get; }

    SqlType DatabaseName { get; }
}

public enum SqlType
{
    MsSql,
    Postgre,
    MySql
}

Usage

internal class Program
{
    static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.RegisterSqlFactory();

        var provider = serviceCollection.BuildServiceProvider();

        var sqlFactory = provider.GetService<ISqlDatabaseFactory>();

        var mySql = sqlFactory.CreateSql(SqlType.MySql);
        var msSql = sqlFactory.CreateSql(SqlType.MsSql);
        var postgre = sqlFactory.CreateSql(SqlType.Postgre);

        Console.WriteLine(
quot;Database Type : {mySql.DatabaseName}, Connectionstring: {mySql.Connecionstring}");
        Console.WriteLine(
quot;Database Type : {msSql.DatabaseName}, Connectionstring: {msSql.Connecionstring}");
        Console.WriteLine(
quot;Database Type : {postgre.DatabaseName}, Connectionstring: {postgre.Connecionstring}");

        Console.ReadKey();
    }
}

Output
enter image description here

Dependencies:

  • .Net Core 3.1
  • Microsoft.Extensions.DependencyInjection;
  • Microsoft.Extensions.Options;
  • System
  • System.Collections.Generic
  • System.Linq;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文