Вопросы:
1. Если прочитать Фаулера по ссылке, видно что у репозитория не должно быть Save.
Не очень понятно, зачем отказываться от Unit of Work, который считается более правильной практикой чем CRUD-методы.
Потому что UoW не работает напрямик с доменными объектами если они не являются объектами Linq-to-Sql?
2. Однако на практике использование атрибута первичного ключа в модели приложения часто приводит к получению даже более гибких схем.
Например?
1. Все верно, UoW более полное решение проблемы ORM. Но, чтобы его развернуть на уровне доменных объектов, требуется значительно больше усилий. На уровне LINQ to SQL UoW и так есть. На самом деле, предлагаемое решение что-то среднее между репозиторием и модулем таблицы.
2. Например, в веб-приложениях можно использовать Id, чтобы идентифицировать объект между двумя разными запросами.
1. Предлагаемое решение это по сути дела CRUD через Linq-to-SQL, то есть лучше чем ADO.NET, но красиво работает только для очень простых случаев (для которых м.б. стоит просто использовать Linq-to-Sql без обёрток?). Для более сложных я бы взял NHibernate/Entity Framework.
2. Но это ведь не значит что Id должен быть свойством самого объекта?
Например можно сделать IdService.GetId(o), которое используется только на UI.
Отличная статья, спасибо!
На сколько я осведомлен о реализации репозитория, в большинстве решений он неразлучен от UnitOfWork, а так же является лишь хранилищем элементов, в то время как UnitOfWork принимает на себя работу со слоем сохраняемости.
Отличный практический пример реализации можно найти в книге ".NET Domain-Driven Design with C#: Problem — Design — Solution" by Tim McCarthy.
>>Репозиторий – это фасад для доступа к базе данных (*)
Мне кажется, данная мысль не соответствует исходной посылке Фаулера. По его мнению, репозиторий — это то, что позволяет оперировать с объектами, соблюдая принцип «Persistence Ignorance», т.е. не задумываясь, есть там БД или нет. У вас же получаются врапперы (правда, более или менее сложные) которые просто изолируют приложение от Linq (и то, не всегда — когда речь заходит об extension methods, используется IQueryable. Конечно, можно сказать, что классы ваших репозиториев могут на самом деле и не работать с БД, но тогда вы будете противоречить сами себе (*).
В реальной практике редко (почти никогда) бывает возможно применить generic-репозитории, как это есть у вас; кроме того, модель CRUD превращается в кошмар, когда нужно работать не с раздельными объектами, а с их отношениями — в этом плане UnitOfWork куда удобнее.
Тем не менее, как пример реализации CRUD на linq, статья полезная, спасибо.
Что касается операций GetAll, Save, Delete, то они сами по себе атомарны. GetAll соответствует одному запросу на чтение и, естественно, атомарен. Save и Delete, хоть и делают два запроса, тоже атомарны — если между двумя запросами происходит изменение соответствующей строки в БД, то кидается ChangeConfictException при вызове SubmitChanges.
Если требуется реализовать транзакционность на более высоком уровне, например, несколько подряд идущих Save, то можно использовать TransactionScope.
транзакция начинает в начале метода Save и заканчивается в конце, лично я так делаю (но использую NHibernate вместо Linq2Sql) тоже самое с Delete, в HBM производительность из-за транзакции на сохранении падает в 10 раз (сам проверял).
кстати как с производительностью при транзакции в Linq2Sql, никто не проверял?
Позвольте уточнить — из-за _явной_ транзакции на сохранении. Вы же не предполагаете, что может быть сохранение вне транзакции в БД? А падение производительности происходит из-за лишнего обращения к серверу БД по сети при обслуживании транзакции.
>> в HBM производительность из-за транзакции на сохранении падает в 10 раз.
Придется Вас огорчить :) С версии 2.0 в NHibernate автоматические транзакции были объявлены небезопасными: вы обязаны выполнять в явной транзакции даже чтение данных
в который раз завидую разработчикам слабонагруженых декстоп систем =) получил всю коллекцию, применил к ней выборки/сортировки, вытащил результат. Хороший патерн, конечно: хорошее расслоение системы.
поясните мне, как человеку плохо знакомому с С# как получается что rep.GetAll().WithNameLike(“Google”).OrderBy(x => x.Name) выполняет лишь один селект? неужели выполняющей функция является OrderBy? или покажите где можно почитать о том как происходит вызов.
просто я вижу эту строчку как-то так:
all = rep.GetAll();
res = all.WithNameLike(“Google”);
res = res..OrderBy(x => x.Name);
очевидно, однако, что это не так.
Фишка в том, что rep.GetAll().WithNameLike(“Google”).OrderBy(x => x.Name) вообще не выполняет селекта.
Он выполнится когда сделают foreach или ToArray/ToList/… — то есть когда неявно вызовут GetEnumerator(), а затем у него MoveNext().
Таким образом, Enumerator.MoveNext здесь и является «выполняющей функцией».
Соответственно
all = rep.GetAll();
res = all.WithNameLike(“Google”);
res = res.OrderBy(x => x.Name);
…
так и работает, т.к. OrderBy как и предыдущие методы просто добавляет новый параметр к запросу.
Сами по себе эти строчки к базе не обращаются.
Наверняка эта строчка только составляет запрос, но не выполняет его. Почитайте «Domain-Specific Embedded Compilers», там эта идея очень хорошо подается.
LINQ to SQL: паттерн Repository