Pull to refresh

Comments 20

О! Как я такое люблю)

В синтетических тестах всё ок, а когда пройдёт год разработки и система усложнится будут кровавые слёзы.
Да там и тестов-то нет никаких.
Ваше заявление безосновательно, кроме использования непонятных абстракций. Я вам скажу так:
1. Я не имею отношения к автору статьи или переводчику;
2. Наша система I-LDS, можете её найти не просторе интернета, содержит больше 350 проектов в основном solution и развивается больше 8 лет коллективом из 10-15 человек, данная система содержит и подобные вещи, по работе с данными. Правда BLToolkit, да и без UOfW, который тут тоже диспосится, но Save не вызывает нигде, насколько я вижу:)
Где мы не правы?:)
данная система содержит и подобные вещи, по работе с данными. Правда BLToolkit, да и без UOfW

Какие «подобные», учитывая, что UoW у вас, по вашим же словам, нет, а BLToolkit с EF ничего общего не имеет (в частности, ни object map, ни change tracking в нем, когда я последний раз смотрел, не было)?
я о общем подходе, а не о частностях реализации. Вы видимо давно смотрели BLTOolkitr, ибо EditableObject там прилично давно, хотя я и стараюсь их не использовать, а реализую свою абстракцию
Тут вся статья — это частности реализации, а общий подход-то и не озвучен. Вы о каком «общем подходе» говорите?
Я говорю о использовании репозитариев, но расширенных, с различными функциями, минуя UoW, но иногда используя EditableObject
Мне показалось, что статья именно об этом
Вам показалось. В этой статье тривиальный обобщенный (то есть заведомо без специфичных расширений) репозиторий, который не имеет ровным счетом никакого added value. При этом в статье как раз есть (избыточный) UoW и никак не упоминается change tracking (хотя он и есть, благодаря чему и возникают описанные мной побочные эффекты). Так что вы описываете подход противоположный тому, что в статье; общего у них только слово «репозиторий» (да и то...).
Вся эта статья вызывает один большой вопрос: «зачем?»

Окей, если вы не верите, то вот много маленьких вопросов «зачем»:
Он ответственен за создание экземпляра нашего 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() (да, с обязательной проверкой).
Это перевод одной статьи из цикла. Там есть развитие этого подхода дальше. Мне было интересно с этим ознакомиться, и может кому — то еще пригодится, потому и перевел… Ваши замечания интересны. Благодарю. Будет время переведу и остальные.
Эта статья бессмысленна — именно тем, что в ней не объясняется, зачем что-то делать. Более того, она еще и опасна — потому что в ней есть архитектурные ошибки.

А где там, простите, развитие подхода, учитывая, что этой статье меньше двух недель, и следующей за ней нет?
Думаю выйдет.
А коменты уже поучительней статьи.
«Думаю, выйдет» — это совсем не то же самое, что «есть развитие подхода дальше».

А «комменты уже поучительнее статьи» — это хороший повод добавить к статье сверху жирное: «в комментах рассказывают, почему так, как в статье, делать не надо». Потому что иначе никто до них не дочитает.
Кстати, поучительный пример того, чего можно достичь таким «прекрасным» подходом.

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»?
Добавлю критики.

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 можно написать конвенцию или так же использовать атрибут, ну а имя таблицы указывать вообще необязательно.
3. Фабрика репозиториев в UoW — допустимый и довольно часто встречающийся подход. Это делается для того, чтобы внедрять в сервисы только один UoW вместо UoW и кучи репозиториев.
6. Репозитории в классической интерпретации не должны возвращать IQueryable объекты, а только объекты, уже загруженные в память.
Фабрика репозиториев в UoW — допустимый и довольно часто встречающийся подход. Это делается для того, чтобы внедрять в сервисы только один UoW вместо UoW и кучи репозиториев.

Но это выходит не UoW, а фасад.


Репозитории в классической интерпретации не должны возвращать IQueryable объекты, а только объекты, уже загруженные в память.

Да, это было бы неплохо. Но в таком случае уже не получится использовать обобщенный репозиторий, каждой сущности понадобится свой уникальный. А значит, возникают проблемы с той же фабрикой...

Одному мне кажется, что в примере можно полностью выпилить UOW и репозиторий и заменить на прямое обращение к контексту и DbSet, и функционал не изменится?
Нет. Выше уже написано.
Sign up to leave a comment.

Articles