Разумеется, все 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();
}
Выбрав правильный agregate root ты избавишь себя от необходимости в одном из двух репозиториев.
ОтветитьУдалитьЗдесь ты в коде задаешь зависимость uow-repository, как ты будешь разрешать ее при использовании IoC контейнера?
@hazzik
ОтветитьУдалитьХороший вопрос, например, через MS Unity использовать класс ParameterOverrides, задав [key="uof", value=uofobj], предварительно разрешив IUnitOfWork на uofobj. Хотя, конечно со стороны это смотрится не очень элегантно.
Не получится так. Если можно приведите пример.
ОтветитьУдалитьIUnitOfWork uof = container.Resolve();
ОтветитьУдалитьParameterOverrides p = new ParameterOverrides();
p.Add("uof", uof);
ICustomerRepository cr = container.Resolve(p);
IOrderRepository or = container.Resolve(p);
О ужс... А если более элегантно? А если этот код будет в нескольких местах, а он будет?
ОтветитьУдалитьВо первых, хочу сказат спасибо за пост.
ОтветитьУдалитьВо вторых, не могли бы вы в дальшейнем прикреплять solution с исходными кодами к постам. В теории все расписано хорошо, но хотелось бы взглянуть и на практическое использование этого (и других) подходов.
@hazzik
ОтветитьУдалитьЯ Вас понимаю, смотрится ужасно, но Unity пока еще не настолько универсален пока. Если у Вас появится решение, мне было бы интересно на него взглянуть.
Как вариант, можно положить все ICustomerRepository и IOrderRepository в IUnityOfWork. И уже в самом IUnityOfWork разрешать типы для репозитариев.
@Dmitry Sukhovilin
Спасибо. Идея с Solution хорошая, но так как я в основном прототипирую его, то редкий Solution скомпилируется :)
Еще есть один интересный подход быстрой разработки, это использование UoW напрямую, а репозитарии (которые реализованы в виде exstention-методов) выполняют роль ТОЛЬКО фильтров данных. Примерно так: UoW+Filters.
@Илья Дубаденко здесь дело не в Unity и не в любом другом контейнере. Для решения этой проблемы нужно смотреть в архитектуру.
ОтветитьУдалитьВаше решение из примера вносит зависимость от конкретных реализаций. А решение из комментариев вносит зависимость от конкретной реализации IoC контейнера.
ОтветитьУдалитьПравильная интеграция uow в приложение очень долгий и тернистый путь, трудозатраты на который просто в большинстве случаев просто не оправданы. Т.к. здесь нужно соблюсти одно очень важное условие - все, что работает внутри юнита должна работать именно с этим юнитом, причем желательно без дополнительного кода. И с активной инжекцией в конструктор.
И поэтому из 2х ваших решений и решения, где Save это обязанность репозитория - я выберу с репозиторием.
@hazzik
ОтветитьУдалитьСогласен, шероховатость есть.
Хорошо, ключевой вопрос, как быть если мы работаем с 2,3,5 и более сущностями, который из репозитариев выполнит конечный Save так, чтобы все изменения выполнились в одной транзакции?
@Илья Дубаденко
ОтветитьУдалитьЕсли эти сущности в одном agregate root - мы просто сохраняем его. А если нет - то, скорее всего все-равно в одной или в 2х/3х/4х транзакциях.
@hazzik
ОтветитьУдалитьТоесть, если не в root, то объект DbTransaction будет инжектироваться в репозитарии? Опять та же проблема. Или все-таки TransactionScope ?
@Илья Дубаденко
ОтветитьУдалитьЯ к тому, что в большинстве приложений никакой UoW не нужен.
@hazzik
ОтветитьУдалить> Выбрав правильный agregate root ты избавишь себя от необходимости в одном из двух репозиториев.
а как вы предлагаете не разделяя агрегат Кастомер/Ордер добираться доступа к отдельным элементам Ордер-списка?
а если например надо отредактировать отдельный Ордер у конкретного Кастомера?
Жалко что на данном вопросе дискуссия закончилась. С одной стороны интересна концепция aggregate root, но слабость данного подхода в проблеме выборки по нужному листу агрегата. Отсюда имеем дополнительные репозитории, количество которых может сравнятся кол-ву сущностей из агрегата.
ОтветитьУдалитьЛично мое мнение, что Repository исчерпал себя как абстракция доступа к данным с появлением UnitofWork, но по привычке мы все еще продолжаем использовать Repository. "Переходным" решением является подход описанный в этой статье (если коротко то Repository over UoW).
ОтветитьУдалитьА проблема выбора aggregate root это уже проблема UoW.
Я не исключаю, что уже внутри UoW применяется подход на основе репозитариев, ведь кто-то должен все-таки делать CRUD.
Ü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}.
ОтветитьУдалить