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