Удаление зависимостей при использовании шаблона DI/абстрактной фабрики

В приведенном ниже упрощенном примере у меня есть DataContext и Repository, которые, как мне кажется, определены довольно разумным образом:

public interface IUnitOfWork 
{
   int SaveChanges();
}

public class DataContext : DbContext, IUnitOfWork 
{
    public DbSet<Car> Cars { get ; set; }
}

public interface ICarsRepository 
{
    Car Find(int id);
    void Add(Car car);
}

public class SqlCarsRepository : ICarsRepository 
{
    private DataContext  _context;
    public SqlCarsRepository(DataContext context)
    {
       _context = context;
    }

    public Car Find(int id) 
    {
        return _context.Cars.Find(id);
    }

    //etc
}

Я изо всех сил пытаюсь понять, как использовать DI и шаблон абстрактной фабрики для достижения того, чего я хотел бы. В приложении MVC это было бы легко настроить — контроллеру потребуются экземпляры реализаций IUnitOfWork и ICarsRepository в его конструкторе. Я мог бы настроить контейнер так, чтобы он предоставлял мне один и тот же экземпляр DataContext для каждого Http-запроса, используя другую фабрику контроллеров. Почему-то кажется, что здесь одноразовые зависимости расположены правильно.

Однако я хотел бы использовать тот же репозиторий в службе Windows. Это многопоточность, и каждый поток при запуске должен иметь доступ к своему собственному репозиторию, и каждый поток должен иметь свой собственный DataContext/UnitOfWork. Но я не знаю, как это сделать:

  • Составной корень приложения — это когда служба запускается, поэтому тогда зависимости не могут быть разрешены для каждого потока (потоки запускаются по требованию).
  • Я не уверен, как я могу использовать абстрактный шаблон factory. Потоку нужны экземпляры IUnitOfWork и ICarsRepository, но совместно использующие один и тот же DataContext. Я могу создать абстрактную фабрику, чтобы создать их за один вызов и передать это в поток, но тогда я не знаю, как избавиться от DataContext. Я не хочу, чтобы поток заботился о том, чтобы зависимости от реализации ICarsRepository были одноразовыми. Я определенно не хочу, чтобы поток знал, что ICarsRepository имеет зависимость от DataContext, потому что тогда кажется бессмысленным иметь интерфейс — поток может просто зависеть от SqlCarsRespository.
  • Я не хочу делать SqlCarsRepository одноразовым и заставлять его удалять DataContext, потому что могут быть другие, использующие DataContext, и он не создавал его в первую очередь.
  • Я думал, что могу создать CarsService, который скрывает IUnitOfWork и ICarsRepository (и получать экземпляры этого с помощью абстрактной фабрики), но я до сих пор не знаю, как избавиться от зависимости DataContext

Каков наилучший способ сделать то, что я пытаюсь?


person Tom Haigh    schedule 28.06.2012    source источник
comment
Какой DI-контейнер вы используете или делаете это вручную?   -  person Steven    schedule 28.06.2012
comment
@Steven: я использую Castle Windsor   -  person Tom Haigh    schedule 29.06.2012


Ответы (1)


поэтому зависимости не могут быть разрешены для каждого потока

Они могут и, по сути, должны. Вы должны разрешать новый граф объектов в начале каждого потока. Невыполнение этого означает, что вы можете использовать только потокобезопасные зависимости, что не так в вашей ситуации.

Я не уверен, как я могу использовать абстрактный шаблон factory

Я думаю, сначала попробуйте определить свой DataContext как Per Web Request в вашем приложении MVC и как Per Lifetime Scope (или что-то еще, доступное в используемом вами контейнере) в вашей службе Windows. В вашей службе Windows каждый поток получит свою собственную область действия. Определение области часто позволяет вам удалять экземпляры, когда область заканчивается.

Я не хочу, чтобы поток заботился о том, чтобы зависимости от реализации ICarsRepository были одноразовыми.

Ваш поток должен заботиться об этом, но не ваша бизнес-логика. При запуске новых потоков вам потребуется некоторый инфраструктурный код, позволяющий запускать и завершать область действия, а также разрешать и использовать корневой тип графа. Этот код должен быть частью корня вашей композиции, чтобы остальная часть приложения не обращала на это внимания. Если вы зарегистрировали некоторые типы с Per Lifetime Scope (или каким-либо другим явным временем жизни), ваш контейнер будет знать, когда удалять экземпляры. Код инфраструктуры должен только сообщить контейнеру, что область действия закончилась.

Я не хочу делать SqlCarsRepository одноразовым

SqlCarsRepository должен зависеть от интерфейса, который не реализует IDisposable, и в этом случае нечего удалять. Это должен быть контейнер, отвечающий за удаление DataContext, и при правильной регистрации вы сможете это сделать.

Каков наилучший способ сделать то, что я пытаюсь?

Ваш дизайн звучит разумно, но вот некоторые другие вопросы SO, которые могут дать вам больше работы:

person Steven    schedule 28.06.2012
comment
Спасибо, это действительно полезно. Таким образом, я могу создать полный изолированный граф объектов для каждого потока при его запуске. Но правила для зависимостей основного потока будут сильно отличаться, потому что они находятся за пределами этой модели, основанной на запросах. Плохо ли иметь два экземпляра контейнера IoC и настроить один для графа для основного потока, а затем другой для потоков? - person Tom Haigh; 29.06.2012
comment
Служба Windows будет отличаться от вашего веб-сайта MVC. Это будет отдельный процесс и (разумеется) будет собственный экземпляр контейнера. Но не только это, так как технология отличается, этот контейнер будет иметь другую конфигурацию. Например, регистрация экземпляров на основе каждого веб-запроса в вашей службе Windows не имеет никакого смысла. Однако наличие нескольких экземпляров контейнера в одном домене приложения — это плохо. Это сделало бы все очень сложным. Используйте этот единственный контейнер для запроса нового графа объектов в начале потока/запроса. - person Steven; 29.06.2012