IOC с несколькими базами данных, которые используют один и тот же интерфейс (StructureMap или любой другой DI Framework)

Мы экспериментировали со 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 для бедняков.


person Beep beep    schedule 15.11.2009    source источник
comment
Я в похожей ситуации. Итак, у меня уже есть 3 разных репозитория, которые вводятся в мой контроллер, и я собираюсь добавить 4-й, и, как и вы, чувствую, что должно быть решение этой проблемы. Ирония в том, что все они имеют базовые методы: Add, Update, FindSingle, FindMany, Delete и SaveChanges, за исключением того, что они вызывают другой контекст. У кого-нибудь есть примеры?   -  person Andrew    schedule 21.11.2016


Ответы (3)


Разве нельзя иметь IClientContext и IMasterContext, возможно, наследуя от IContext. Я чувствую, что код будет делать одну из двух очень разных вещей в зависимости от того, разговариваете ли вы с базой данных «Мастер» или «Клиент».

person Richard Nienaber    schedule 15.11.2009

В Unity вы можете иметь именованные регистрации, что позволяет эффективно зарегистрировать больше, чем класс для данного интерфейса. . Итак, вы можете сделать (набрав наизусть, проверьте фактическую документацию Unity, если интересно):

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

и тогда конструктор для SomeService будет:

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

Недостатком является то, что таким образом ваш класс обслуживания больше не зависит от используемой инфраструктуры DI, а зависит от проекта, который может быть в порядке.

person Konamiman    schedule 15.11.2009

Это может быть немного сложно, если вы полагаетесь на 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 может ссылаться на сборку (или что вы действительно хотите)

person John Foster    schedule 15.11.2009
comment
На самом деле мы используем их повсеместно (десятки контроллеров). На более низких уровнях мы могли бы разделить его, чтобы для каждого проекта использовался только 1 контекст (у нас есть отдельные проекты Client и Master), но я не видел, как я мог бы настроить StructureMap для разного разрешения по проекту/сборке. - person Beep beep; 15.11.2009
comment
Обновлено в отношении решения по-разному в сборке - person John Foster; 15.11.2009