Feel Good.

11 мая 2010

Совместное использование Repository с Unit Of Work.

В этом посте я решил поделиться мыслями о том, как можно применить паттерн Repository совместно с паттерном Unit Of Work (UOW). Рассмотрим это на конкретном примере. Для этого введем две сущности: Customer и Order, состоящим в отношении один-ко-многим соответственно.

Разумеется, все CRUD действия над сущностями будут производиться в классах репозитариях, не содержащих Save/Commit методов:

// Класс репозиторий для ICustomer

interface ICustomerRepository : IRepository<ICustomer>

{

}

 

// Класс репозиторий для IOrder

interface IOrderRepository : IRepository<IOrder>

{

    // Связанный с ICustomer список IOrder.

    IQueryable<IOrder> GetCustomerOrders(ICustomer customer);

}


где IRepository общий для всех репозитариев тип:

interface IRepository<TEntity>

{

    TEntity GetById(int id);

    void Add(TEntity entity);

 

    // И прочие общие CRUD методы...

    // Но НЕТ никаких "Save" или "Commit" методов!

}


Заметьте, так как указанные репозитории не содержат методы Save и Commit, то данную функциональность берет на себя Unit of work контейнер:

// Наш Unit Of Work контейнер

public interface IUnitOfWork : IDisposable

{

    // Здесь можно указать Undo/Rollback функции.

 

    void Save();

}


Например, реализуем "наш" UOW для Entity FrameWork:

// Реализация для Entity Framework либо Linq To Sql

partial class MyDataContext : ObjectContext

{

    // То что нам сгенерировал кодогенератор...       

}

 

 

// Расширим через partial

partial class MyDataContext : IUnitOfWork

{

    public void Save()

    {

        // Вызываем внутренний SaveChanges:

        this.SaveChanges();

    }               

}


Приступим к реализации класса репозитария для сущности ICustomer, конструктор которого инжектирует "наш" абстрактный IUnitOfWork:

class EFCustomerRepository : ICustomerRepository

{

    // Далее, в классе используем конкретный context.

    MyDataContext DataContext { get; set; }

 

    // Инжектируем IUnitOfWork

    public EFCustomerRepository(IUnitOfWork uof)

    {

        DataContext = uof as MyDataContext;

    }

}


Видно, что в примере существует жесткая связь EFCustomerRepository от MyDataContext, которую можно исключить, только расширив интерфейс IUnitOfWork. И аналогично для репозитария IOrderRepository. С учетом сказанного выше, приведу пример использования:

// все действия будем производить в одном UOW

using (IUnitOfWork uof = new MyDataContext())

{

    ICustomerRepository cr = new EFCustomerRepository(uof);

    IOrderRepository or = new EFOrderRepository(uof);

 

    // CRUD действия с объектами

 

    // Завершаем транзакцию.

    uof.Save();

}

17 комментариев:

  1. Выбрав правильный agregate root ты избавишь себя от необходимости в одном из двух репозиториев.

    Здесь ты в коде задаешь зависимость uow-repository, как ты будешь разрешать ее при использовании IoC контейнера?

    ОтветитьУдалить
  2. @hazzik

    Хороший вопрос, например, через MS Unity использовать класс ParameterOverrides, задав [key="uof", value=uofobj], предварительно разрешив IUnitOfWork на uofobj. Хотя, конечно со стороны это смотрится не очень элегантно.

    ОтветитьУдалить
  3. Не получится так. Если можно приведите пример.

    ОтветитьУдалить
  4. IUnitOfWork uof = container.Resolve();
    ParameterOverrides p = new ParameterOverrides();
    p.Add("uof", uof);
    ICustomerRepository cr = container.Resolve(p);
    IOrderRepository or = container.Resolve(p);

    ОтветитьУдалить
  5. О ужс... А если более элегантно? А если этот код будет в нескольких местах, а он будет?

    ОтветитьУдалить
  6. Во первых, хочу сказат спасибо за пост.
    Во вторых, не могли бы вы в дальшейнем прикреплять solution с исходными кодами к постам. В теории все расписано хорошо, но хотелось бы взглянуть и на практическое использование этого (и других) подходов.

    ОтветитьУдалить
  7. @hazzik
    Я Вас понимаю, смотрится ужасно, но Unity пока еще не настолько универсален пока. Если у Вас появится решение, мне было бы интересно на него взглянуть.
    Как вариант, можно положить все ICustomerRepository и IOrderRepository в IUnityOfWork. И уже в самом IUnityOfWork разрешать типы для репозитариев.

    @Dmitry Sukhovilin
    Спасибо. Идея с Solution хорошая, но так как я в основном прототипирую его, то редкий Solution скомпилируется :)

    Еще есть один интересный подход быстрой разработки, это использование UoW напрямую, а репозитарии (которые реализованы в виде exstention-методов) выполняют роль ТОЛЬКО фильтров данных. Примерно так: UoW+Filters.

    ОтветитьУдалить
  8. @Илья Дубаденко здесь дело не в Unity и не в любом другом контейнере. Для решения этой проблемы нужно смотреть в архитектуру.

    ОтветитьУдалить
  9. Ваше решение из примера вносит зависимость от конкретных реализаций. А решение из комментариев вносит зависимость от конкретной реализации IoC контейнера.

    Правильная интеграция uow в приложение очень долгий и тернистый путь, трудозатраты на который просто в большинстве случаев просто не оправданы. Т.к. здесь нужно соблюсти одно очень важное условие - все, что работает внутри юнита должна работать именно с этим юнитом, причем желательно без дополнительного кода. И с активной инжекцией в конструктор.

    И поэтому из 2х ваших решений и решения, где Save это обязанность репозитория - я выберу с репозиторием.

    ОтветитьУдалить
  10. @hazzik

    Согласен, шероховатость есть.
    Хорошо, ключевой вопрос, как быть если мы работаем с 2,3,5 и более сущностями, который из репозитариев выполнит конечный Save так, чтобы все изменения выполнились в одной транзакции?

    ОтветитьУдалить
  11. @Илья Дубаденко
    Если эти сущности в одном agregate root - мы просто сохраняем его. А если нет - то, скорее всего все-равно в одной или в 2х/3х/4х транзакциях.

    ОтветитьУдалить
  12. @hazzik

    Тоесть, если не в root, то объект DbTransaction будет инжектироваться в репозитарии? Опять та же проблема. Или все-таки TransactionScope ?

    ОтветитьУдалить
  13. @Илья Дубаденко
    Я к тому, что в большинстве приложений никакой UoW не нужен.

    ОтветитьУдалить
  14. @hazzik
    > Выбрав правильный agregate root ты избавишь себя от необходимости в одном из двух репозиториев.

    а как вы предлагаете не разделяя агрегат Кастомер/Ордер добираться доступа к отдельным элементам Ордер-списка?

    а если например надо отредактировать отдельный Ордер у конкретного Кастомера?

    ОтветитьУдалить
  15. Жалко что на данном вопросе дискуссия закончилась. С одной стороны интересна концепция aggregate root, но слабость данного подхода в проблеме выборки по нужному листу агрегата. Отсюда имеем дополнительные репозитории, количество которых может сравнятся кол-ву сущностей из агрегата.

    ОтветитьУдалить
  16. Лично мое мнение, что Repository исчерпал себя как абстракция доступа к данным с появлением UnitofWork, но по привычке мы все еще продолжаем использовать Repository. "Переходным" решением является подход описанный в этой статье (если коротко то Repository over UoW).
    А проблема выбора aggregate root это уже проблема UoW.
    Я не исключаю, что уже внутри UoW применяется подход на основе репозитариев, ведь кто-то должен все-таки делать CRUD.

    ОтветитьУдалить
  17. Üniversite tavlası is a variant of the game performed with two or more tavlas and four or more players, with the players forming teams. The dice are thrown only by two opposing players and the rest should play the identical dice. If a staff member will get crushed and cannot enter, his teammates cannot play for that spherical. Although the dice are the identical, the game on every board differs, the place 텐벳 the case of 1 staff member successful and another losing fairly common}.

    ОтветитьУдалить