Comments 20
О! Как я такое люблю)
В синтетических тестах всё ок, а когда пройдёт год разработки и система усложнится будут кровавые слёзы.
В синтетических тестах всё ок, а когда пройдёт год разработки и система усложнится будут кровавые слёзы.
+4
Да там и тестов-то нет никаких.
0
Ваше заявление безосновательно, кроме использования непонятных абстракций. Я вам скажу так:
1. Я не имею отношения к автору статьи или переводчику;
2. Наша система I-LDS, можете её найти не просторе интернета, содержит больше 350 проектов в основном solution и развивается больше 8 лет коллективом из 10-15 человек, данная система содержит и подобные вещи, по работе с данными. Правда BLToolkit, да и без UOfW, который тут тоже диспосится, но Save не вызывает нигде, насколько я вижу:)
Где мы не правы?:)
1. Я не имею отношения к автору статьи или переводчику;
2. Наша система I-LDS, можете её найти не просторе интернета, содержит больше 350 проектов в основном solution и развивается больше 8 лет коллективом из 10-15 человек, данная система содержит и подобные вещи, по работе с данными. Правда BLToolkit, да и без UOfW, который тут тоже диспосится, но Save не вызывает нигде, насколько я вижу:)
Где мы не правы?:)
-2
данная система содержит и подобные вещи, по работе с данными. Правда BLToolkit, да и без UOfW
Какие «подобные», учитывая, что UoW у вас, по вашим же словам, нет, а BLToolkit с EF ничего общего не имеет (в частности, ни object map, ни change tracking в нем, когда я последний раз смотрел, не было)?
0
я о общем подходе, а не о частностях реализации. Вы видимо давно смотрели BLTOolkitr, ибо EditableObject там прилично давно, хотя я и стараюсь их не использовать, а реализую свою абстракцию
0
Тут вся статья — это частности реализации, а общий подход-то и не озвучен. Вы о каком «общем подходе» говорите?
+1
Я говорю о использовании репозитариев, но расширенных, с различными функциями, минуя UoW, но иногда используя EditableObject
Мне показалось, что статья именно об этом
Мне показалось, что статья именно об этом
0
Вам показалось. В этой статье тривиальный обобщенный (то есть заведомо без специфичных расширений) репозиторий, который не имеет ровным счетом никакого added value. При этом в статье как раз есть (избыточный) UoW и никак не упоминается change tracking (хотя он и есть, благодаря чему и возникают описанные мной побочные эффекты). Так что вы описываете подход противоположный тому, что в статье; общего у них только слово «репозиторий» (да и то...).
+2
Вся эта статья вызывает один большой вопрос: «зачем?»
Окей, если вы не верите, то вот много маленьких вопросов «зачем»:
А зачем нужно, чтобы все репозитории работали с одним и тем же контекстом данных? И почему это нельзя сделать без UoW?
А зачем нужен этот класс? Особенно учитывая, что:
но
То есть даже навязанные свойства не используются.
Во-первых, снова не объяснили, зачем. А во-вторых — для какого EF это написано? В шестом есть прекрасный метод AddFromAssembly.
А вот теперь — самое важное «зачем». Скажите, пожалуйста, зачем нужен репозиторий, который не делает ничего, чего не делал бы DbContext? От текущей абстракции
Ну и еще не «зачем», а просто так:
Вот только на картинке это не нарисовано. Нельзя взаимодействовать с репозиторием через EF, потому что репозиторий — это абстракция над EF, а не наоборот.
Вообще-то можно просто передать строку подключения.
Так делать тоже не надо. Это приводит к тому, что на уровне БД EF считает, что колонки обязательные, а на уровне модели — нет. Намного правильнее разметить свойства модели атрибутом
Нет, нельзя. Изнутри репозитория нельзя использовать метод
Какая религия запрещает сделать
И там же:
Метод
Окей, если вы не верите, то вот много маленьких вопросов «зачем»:
Он ответственен за создание экземпляра нашего DbContext, в следствие чего, все все репозитории будут использовать один и тот же DbContext для работы с БД. То есть паттерн Unit of Work гарантирует, что все репозитории работают с одним контекстом данных.
А зачем нужно, чтобы все репозитории работали с одним и тем же контекстом данных? И почему это нельзя сделать без UoW?
класс BaseEntity
, в котором описываем общие свойства для наследования каждой сущностью
А зачем нужен этот класс? Особенно учитывая, что:
public abstract class BaseEntity { public Int64 ID { get; set; } public DateTime AddedDate { get; set; } public DateTime ModifiedDate { get; set; } public string IP { get; set; } }
но
public class Repository<T> where T : BaseEntity { public T GetById(object id) { return this.Entities.Find(id); } }
То есть даже навязанные свойства не используются.
public class EFDbContext : DbContext { protected override void OnModelCreating(DbModelBuilder modelBuilder) { var typesToRegister = Assembly.GetExecutingAssembly().GetTypes() } }
Во-первых, снова не объяснили, зачем. А во-вторых — для какого EF это написано? В шестом есть прекрасный метод AddFromAssembly.
В репозитории будут реализованы все операции CRUD.
А вот теперь — самое важное «зачем». Скажите, пожалуйста, зачем нужен репозиторий, который не делает ничего, чего не делал бы DbContext? От текущей абстракции
IQueryable
вы не избавляетесь — она у вас явно выставлена наружу. Никакой полезной работы (удаления по id или обновления отсоединенной сущности) у вас нет. В чем смысл? На пустом месте сделано два слоя (repository и unit of work), хотя они прекрасно делаются четырьмя generic-методами в контексте.Ну и еще не «зачем», а просто так:
На следующем изображении показана взаимосвязь между репозиторием и контекстом данных Entity Framework, в котором контроллеры взаимодействуют с репозиторием через Unit of Work, а не непосредственно через Entity Framework.
Вот только на картинке это не нарисовано. Нельзя взаимодействовать с репозиторием через EF, потому что репозиторий — это абстракция над EF, а не наоборот.
Так же следует напомнить, что концепция «сначала код» следует конвенции по конфигурации, поэтому необходимо передать в конструктор строку с названием соединения и которая в точности совпадает с таковой в настройка приложения в файле App.Config.
Вообще-то можно просто передать строку подключения.
public class BookMap : EntityTypeConfiguration<Book> { public BookMap() { HasKey(t => t.ID); Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); Property(t => t.Title).IsRequired(); Property(t => t.Author).IsRequired(); Property(t => t.ISBN).IsRequired(); Property(t => t.Published).IsRequired(); ToTable("Books"); } }
Так делать тоже не надо. Это приводит к тому, что на уровне БД EF считает, что колонки обязательные, а на уровне модели — нет. Намного правильнее разметить свойства модели атрибутом
Required
, после чего не только EF автоматически подтянет валидацию (и свойства БД), но и все остальные слои приложения — тоже.Мы будем использовать метод saveChanges() контекста, однако можно использовать и метод save() класса Unit of Work, так как у них обоих будет один и тот же контекст данных.
Нет, нельзя. Изнутри репозитория нельзя использовать метод
Save
от UoW, потому что репозиторий ничего не знает о UoW. А для любого потребителя метод Save
от UoW не имеет смысл, потому что все изменения всегда сохраняются внутри репозитория.public class UnitOfWork : IDisposable { public Repository<T> Repository<T>() where T : BaseEntity { if (repositories == null) { repositories = new Dictionary<string,object>(); } var type = typeof(T).Name; if (!repositories.ContainsKey(type)) { var repositoryType = typeof(Repository<>); var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), context); repositories.Add(type, repositoryInstance); } return (Repository<t>)repositories[type]; } }
Какая религия запрещает сделать
new Repository<T>(context)
? Зачем нужен словарь репозиториев (да еще и по именам без пространств имен, так что коллизии прямо за углом)?И там же:
public void Save() { context.SaveChanges(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { context.Dispose(); } } disposed = true; }
Метод
Save
, как уже говорилось, не нужен. А всю реализацию Dispose
можно заменить одной строчкой: if (context != null) context.Dispose()
(да, с обязательной проверкой).+5
Это перевод одной статьи из цикла. Там есть развитие этого подхода дальше. Мне было интересно с этим ознакомиться, и может кому — то еще пригодится, потому и перевел… Ваши замечания интересны. Благодарю. Будет время переведу и остальные.
0
Эта статья бессмысленна — именно тем, что в ней не объясняется, зачем что-то делать. Более того, она еще и опасна — потому что в ней есть архитектурные ошибки.
А где там, простите, развитие подхода, учитывая, что этой статье меньше двух недель, и следующей за ней нет?
А где там, простите, развитие подхода, учитывая, что этой статье меньше двух недель, и следующей за ней нет?
+4
Думаю выйдет.
А коменты уже поучительней статьи.
А коменты уже поучительней статьи.
0
Кстати, поучительный пример того, чего можно достичь таким «прекрасным» подходом.
Внимание, вопрос: как сейчас в БД назвается продавец с идентификатором «1»?
var productRepository = uow.Repository<Product>();
var product = productRepository.GetById(15);
product.Name = "Good";
var sellerRepository = uow.Repository<Seller>();
var seller = sellerRepository.GetById(1);
seller.Name = "Ouch!";
productRepository.Update(product);
Внимание, вопрос: как сейчас в БД назвается продавец с идентификатором «1»?
+2
Добавлю критики.
1. У репозитория нет интерфейса, и ни слова про тесты. А ведь именно модульные тесты, которые необходимо абстрагировать от контекста БД, и являются единственным оправданием существованию репозиториев как отдельной сущности. Во всех остальных случаях очень хорошей реализацией репозитория является
2. Ни слова про транзакции — между тем, многого без транзакций не сделать. Использование
3. Что вообще в UoW делает фабрика репозиториев? UoW не должен решать никаких задач, кроме управления записью в БД.
4. UoW не должен вызывать
4+. Что будет, если к одному и тому же контексту будут применены два UoW — но у первого не будет вызван метод сохранения? Ответ: сохранение произойдет во втором. А это — совсем не то, что ожидалось. Поэтому UoW должен либо производить очистку контекста (а это нетривиальная задача, хотя и не сильно сложная), либо просто не создаваться на «грязном» контексте (защитное программирование).
Также нельзя забывать и про защиту контекста от изменений за пределами UoW или после сохранения. Разумеется, все это актуально только лишь если контекст не разрушается при выходе из UoW.
5. Если у репозитория отобрать функцию сохранения (потому что это вообще-то задача UoW) — то метод
6. Репозиторию не хватает кучи действительно полезных операций. К примеру, для многоуровневой архитектуры очень пригодилась бы операция
А еще мне нравится операция, которую я называю
Еще вариант этой операции принимает не id сущности, а саму сущность. Смысл в том, что для сущности, загруженной из БД, делается запрос — а для сущности, которая была добавлена в репозиторий, но еще не сохранена, возвращается массив. Это упрощает многие операции
7. Класс
1. У репозитория нет интерфейса, и ни слова про тесты. А ведь именно модульные тесты, которые необходимо абстрагировать от контекста БД, и являются единственным оправданием существованию репозиториев как отдельной сущности. Во всех остальных случаях очень хорошей реализацией репозитория является
IDbSet<>
, а удовлетворительной реализацией UnitOfWork
— сам контекст.2. Ни слова про транзакции — между тем, многого без транзакций не сделать. Использование
TransactionScope
при работе с всего одной базой — не лучший вариант, поскольку при каждом чихе транзакция пытается стать распределенной. Следовало бы либо добавить транзакции в общий UoW — либо сделать отдельный UoW с транзакциями.3. Что вообще в UoW делает фабрика репозиториев? UoW не должен решать никаких задач, кроме управления записью в БД.
4. UoW не должен вызывать
Dispose
у контекста! Общее правило: не ты порождал — не тебе и убивать. Исключение — явная спецификация владения, такая как Owned<>
в Autofac.4+. Что будет, если к одному и тому же контексту будут применены два UoW — но у первого не будет вызван метод сохранения? Ответ: сохранение произойдет во втором. А это — совсем не то, что ожидалось. Поэтому UoW должен либо производить очистку контекста (а это нетривиальная задача, хотя и не сильно сложная), либо просто не создаваться на «грязном» контексте (защитное программирование).
Также нельзя забывать и про защиту контекста от изменений за пределами UoW или после сохранения. Разумеется, все это актуально только лишь если контекст не разрушается при выходе из UoW.
5. Если у репозитория отобрать функцию сохранения (потому что это вообще-то задача UoW) — то метод
Update
окажется пустым. Поскольку EF делает проверку всех сущностей на изменения, он не нужен.6. Репозиторию не хватает кучи действительно полезных операций. К примеру, для многоуровневой архитектуры очень пригодилась бы операция
Attach
.А еще мне нравится операция, которую я называю
Fetch
. Она работает так же, как и GetById
— но возвращает не объект, а IQueryable<>
, содержащий этот единственный объект. Смысл в том, что вокруг такой операции можно нарастить сложный запрос, который в противном случае привел бы к множественным срабатываниям ленивой загрузки вложенных записей.Еще вариант этой операции принимает не id сущности, а саму сущность. Смысл в том, что для сущности, загруженной из БД, делается запрос — а для сущности, которая была добавлена в репозиторий, но еще не сохранена, возвращается массив. Это упрощает многие операции
7. Класс
BookMap
не имеет особого смысла, занимая место в проекте. Такие классы хорошо смотрятся, когда сущности были сгенерированы по готовой БД — но не в Code First
. Когда сущностей становится 50, лишние файлы начинают сильно мешаться. Как уже было написано, обязательность поля желательно указывать атрибутом, про поле Id
можно написать конвенцию или так же использовать атрибут, ну а имя таблицы указывать вообще необязательно.+2
3. Фабрика репозиториев в UoW — допустимый и довольно часто встречающийся подход. Это делается для того, чтобы внедрять в сервисы только один UoW вместо UoW и кучи репозиториев.
6. Репозитории в классической интерпретации не должны возвращать IQueryable объекты, а только объекты, уже загруженные в память.
6. Репозитории в классической интерпретации не должны возвращать IQueryable объекты, а только объекты, уже загруженные в память.
0
Фабрика репозиториев в UoW — допустимый и довольно часто встречающийся подход. Это делается для того, чтобы внедрять в сервисы только один UoW вместо UoW и кучи репозиториев.
Но это выходит не UoW, а фасад.
Репозитории в классической интерпретации не должны возвращать IQueryable объекты, а только объекты, уже загруженные в память.
Да, это было бы неплохо. Но в таком случае уже не получится использовать обобщенный репозиторий, каждой сущности понадобится свой уникальный. А значит, возникают проблемы с той же фабрикой...
0
Одному мне кажется, что в примере можно полностью выпилить UOW и репозиторий и заменить на прямое обращение к контексту и DbSet, и функционал не изменится?
0
Просто статья, перевод гайда индуса www.codeproject.com/Articles/814768/CRUD-Operations-Using-the-Generic-Repository-Patte
Этим я думаю все сказано ;)
Этим я думаю все сказано ;)
+1
Sign up to leave a comment.
CRUD Operations Using the Generic Repository Pattern and Unit of Work in MVC