Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
данная система содержит и подобные вещи, по работе с данными. Правда BLToolkit, да и без UOfW
Он ответственен за создание экземпляра нашего DbContext, в следствие чего, все все репозитории будут использовать один и тот же DbContext для работы с БД. То есть паттерн Unit of Work гарантирует, что все репозитории работают с одним контекстом данных.
класс 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() } }
В репозитории будут реализованы все операции CRUD.
IQueryable вы не избавляетесь — она у вас явно выставлена наружу. Никакой полезной работы (удаления по id или обновления отсоединенной сущности) у вас нет. В чем смысл? На пустом месте сделано два слоя (repository и unit of work), хотя они прекрасно делаются четырьмя generic-методами в контексте.На следующем изображении показана взаимосвязь между репозиторием и контекстом данных Entity Framework, в котором контроллеры взаимодействуют с репозиторием через Unit of Work, а не непосредственно через Entity Framework.
Так же следует напомнить, что концепция «сначала код» следует конвенции по конфигурации, поэтому необходимо передать в конструктор строку с названием соединения и которая в точности совпадает с таковой в настройка приложения в файле 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"); } }
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);
IDbSet<>, а удовлетворительной реализацией UnitOfWork — сам контекст.TransactionScope при работе с всего одной базой — не лучший вариант, поскольку при каждом чихе транзакция пытается стать распределенной. Следовало бы либо добавить транзакции в общий UoW — либо сделать отдельный UoW с транзакциями.Dispose у контекста! Общее правило: не ты порождал — не тебе и убивать. Исключение — явная спецификация владения, такая как Owned<> в Autofac.Update окажется пустым. Поскольку EF делает проверку всех сущностей на изменения, он не нужен.Attach.Fetch. Она работает так же, как и GetById — но возвращает не объект, а IQueryable<>, содержащий этот единственный объект. Смысл в том, что вокруг такой операции можно нарастить сложный запрос, который в противном случае привел бы к множественным срабатываниям ленивой загрузки вложенных записей.BookMap не имеет особого смысла, занимая место в проекте. Такие классы хорошо смотрятся, когда сущности были сгенерированы по готовой БД — но не в Code First. Когда сущностей становится 50, лишние файлы начинают сильно мешаться. Как уже было написано, обязательность поля желательно указывать атрибутом, про поле Id можно написать конвенцию или так же использовать атрибут, ну а имя таблицы указывать вообще необязательно.Фабрика репозиториев в UoW — допустимый и довольно часто встречающийся подход. Это делается для того, чтобы внедрять в сервисы только один UoW вместо UoW и кучи репозиториев.
Но это выходит не UoW, а фасад.
Репозитории в классической интерпретации не должны возвращать IQueryable объекты, а только объекты, уже загруженные в память.
Да, это было бы неплохо. Но в таком случае уже не получится использовать обобщенный репозиторий, каждой сущности понадобится свой уникальный. А значит, возникают проблемы с той же фабрикой...
CRUD Operations Using the Generic Repository Pattern and Unit of Work in MVC