ASP.NET MVC: Unit of Work или продолжаем оптимизировать сайт

Почему Unit of Work

В проекте “Музей юмора” у меня используется достаточно больше количество репозиториев. В этой статье реализуем паттерн “Unit of Work”. Предвижу возможный вопрос: “для чего?”. На сайте asp.net о причине использования данного паттерна описано так:

The unit of work class serves one purpose: to make sure that when you use multiple repositories, they share a single database context.

Что, в вольном переводе автора статьи звучит как:

Класс “единица работы” служит одной цели: чтобы была уверенность в том, что при использовании множества репозиториев, которые работают с базой данных,  работа осуществлялась с одним и тем же экземпляром DbContext.

В своем приложении я использовал контейнер UnityContainer для разрешения экземпляров классов (resolve), ну, и, соответственно, для инъекций вливания (Dependency Injection). На самом деле, существует огромное количество контейнеров, каждый из которых, в свою очередь, так или иначе, позволяет использовать экземпляры некоторых классов в режиме “единственный экземпляр” (Singleton). Не плохо с этим справляется и UnityContainer (использование PerThreadLifetimeManager или HierarchicalLifetimeManager), но в силу ограниченности серверных ресурсов (ограничения хост-провайдера, см. предыстории часть 1 и часть 2), пришлось искать альтернативное решение.

В результате большого количества тестов направленных в основном на использование памяти (private memory), выяснилось, что принцип управления жизненным циклом экземпляра DbContext лучше всего “взять в свои руки”, а не полагаться на универсальные механизмы примененные в UnityContainer. Таким образом, если говорить о концепции “один запрос – одни DbContext” (one DbContext per request), то использование “единицы работы” (Unit of Work) позволит несколько другим способом контролировать уникальность контекста базы данных (DbContext) на один запрос.

Unit of Work (UoW) — описание

О принципе UoW можно кратко сказать так, что UoW объединяет репозитории для работы с одним и тем же экземпляром базы данных. Если у вас более сложная структура данных, возможно потребуется более чем одни DbContext, и, соответственно, более чем один Unit of Work. В случае с проектом “Музей юмора”, имеет смысл один DbContext, и, соответственно, одни Unit of Work.

UoW имеет у себя практически единственный метод Commit, который в выполняет метод SaveChange у DbContext:

1: publicinterface IMuseumUoW { 2: 3: // Сохранение данных 4: // ожидающих сохранения 5: void Commit(); 6: 7: // Репозитории 8: IRepository<Exhibit> Exhibits { get; } 9: IRepository<Hall> Halls { get; } 10: IRepository<LinkItem> LinkItems { get; } 11: IRepository<LinkCatalog> LinkCatalogs { get; } 12: ISubscriberRepository Subscribers { get; } 13: ILentaRepository Lentas { get; } 14: ILogRepository Logs { get; } 15: ITagRepository Tags { get; } 16: }

Думаю, вы обратили внимание на некоторое количество репозиториев, которые перекочевали сюда из инициализации Bootstrapper’e (см. статью “Всё ради данных”).

Nuget-пакет Mvc.UnitOfWork

Небольшое лирическое отступление связано с ленью. Чтобы не писать один и тот же код много раз, я в очередной раз создал nuget-пакет. Пакет называется Mvc.UnitOfWork. В нем содержится некоторое количество классов и интерфейсов, использование которых существенно ускорят процесс внедрения паттерна Unit of Work в вашем ASP.NET MVC проекте.

Откуда пакеты? С Nuget’а вестимо!

Установим новый пакет:

PM> Install-Package Mvc.UnitOfWork
Attempting to resolve dependency ‘EntityFramework (≥ 4.3.0)’.
Successfully installed ‘Mvc.UnitOfWork 0.1.0’.
Successfully added ‘Mvc.UnitOfWork 0.1.0’ to Calabonga.Mvc.Humor.

PM>

Интерфейсы репозиториев и их реализации

Теперь придется немного переделать интерфейсы. Например, IExhibitRepository, которые был ранее в статьях “История одного проекта” описан как:

1: publicinterface IExhibitRepository { 2: IQueryable<Exhibit> All { get; } 3: IQueryable<Exhibit> AllIncluding(params Expression<Func<Exhibit, object>>[] includeProperties); 4: Exhibit Find(int id); 5: void Insert(Exhibit item); 6: void Update(Exhibit item); 7: void Delete(int id); 8: void Save(); 9: }

В силу того, что реализация интерфейсов убрана в обобщенные базовые классы (в nuget-пакете валяются):

1: publicinterface IRepository<T> where T : class { 2: IQueryable<T> All(); 3: IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties); 4: T GetById(int id); 5: void Add(T entity); 6: void Update(T entity); 7: void Delete(T entity); 8: void Delete(int id); 9: }

Теперь мой интерфейс будет выглядеть так:

1: publicinterface IExhibitRepository : IRepository<Exhibit> { 2: }

С реализацией базовых интерфейсов (interface) всё заметно упростилось, не правда ли? А теперь реализуем IExhibitRepository, описанный ранее.

1: publicclass ExhibitRepository : EntityRepository<Exhibit>, IExhibitRepository { 2: public ExhibitRepository(DbContext context) : base(context) { } 3: }

Этого достаточно, чтобы получит контроль надо CRUD-операциями этой сущности (Create/Read/Update/Delete = CRUD).

Но что делать, если нам потребуется немного больше свойств и методов, нежели уже описанных в базовых классах. Примером тому сущность “метка” (Tag). У меня в проекте обязательно требуется дополнительный метод, который “умеет” искать метку по имени, а не только по идентификатору, как указано в базовом интерфейсе (см. метод GetById).

Расширяемся

Расширим базовый репозиторий. Теперь мой ITagRepository будет выглядеть так:

1: publicinterface ITagRepository : IRepository<Tag> { 2: Tag FindName(string name); 3: }

Более того, если я не хочу использовать предопределенные методы и свойства, их можно легко переопределить, потому что они помечены модификатором virtual. Итак, реализация ITagRepository теперь будет такая:

1: publicclass TagRepository : EntityRepository<Tag>, ITagRepository { 2: public TagRepository(DbContext context) : base(context) { } 3: 4: public Tag FindName(string name) { 5: return DbSet.SingleOrDefault(x => x.Name.ToLower().Equals(name.ToLower())); 6: } 7: }

Обратите внимание, как и прошлой реализации для интерфейса IExhibitRepository, мой TagRepository унаследован от базового класса EntityRepository<T>, а в базовый конструктор “проваливается” экземпляр класса DbContext.

И еще одна немаловажная вещь. Для доступа к таблице (DbSet<T>) в базе данных (DbContext) нужно использовать (если вы будете использовать nuget-пакет Mvc.UnityOfWork) свойство DbSet, как показано в предыдущем листинге.

Unit of Work (UoW) – реализация

Так как выше вначале статьи IMuseumUow уже приведен полностью, давайте создадим класс-реализацию этого интерфейса (весь список будет в полном листинге в конце статьи). Но для начала унаследуем наш класс от базового класса из nuget-пакета UnitOfWorkBase:

1: /// <summary> 2: /// Main class Unit of Work for Museum of Humor 3: /// </summary> 4: publicclass MuseumUoW : UnitOfWorkBase, IMuseumUoW, IDisposable { 5:   6: public MuseumUoW(IRepositoryProvider provider) 7: : base(provider) { 8: InitializeDbContext(); 9: } 10:   11: // часть кода скрыта для краткости 12: }

В строке номер 8 инициализируем DbContext, при этом сразу задаем параметры и вызываем инициализацию провайдера репозиториев:

1: publicoverridevoid InitializeDbContext() { 2: DbContext = new MuseumContext(); 3:   4: // запретим создавать proxy-классы для сущностей 5: // чтобы избежать проблем при сериализации объектов 6: DbContext.Configuration.ProxyCreationEnabled = false; 7:   8: // Отключим неявную «ленивую» загрузку 9: // (избежим проблемы с сериализацией) 10: DbContext.Configuration.LazyLoadingEnabled = false; 11:   12: // Отключим для повышения увеличения производительности 13: // валидацию при записи в базу данных. 14: // Я ее отключил, потому что уверен в том, что 15: // реализую валидацию объектов на форме (представления), 16: // а при реализации Web API я буду использовать валидацию 17: // для Knockout.Validation 18: DbContext.Configuration.ValidateOnSaveEnabled = false; 19:   20: // Инициализируем провайдера репозиториев 21: InitializeProvider(this.DbContext); 22: }

В комментария кода всё описал “что” и “для чего”. Регистрируем репозитории, которые потребуются для работы Unit of Work:

1: // … тут другие репозитории 2: 3: public IRepository<LinkCatalog> LinkCatalogs { 4: get { 5: return GetRepository<LinkCatalog>(); 6: } 7: } 8: public ISubscriberRepository Subscribers { 9: get { 10: return GetRepositoryExt<ISubscriberRepository>(); 11: } 12: } 13:  

Я сократил список регистрируемых репозиториев (полный в конце, хотя и ни к чему). Вы обратили внимания, что в конструктор MuseumUoW через инъекцию (Dependency Injection) вливается IRepositoryProvider. И на последок, реализуем IDisposable в нашем классе, мы же не хотим, чтобы были утечки памяти? Наверное, я приведу весь код моего класс MuseumUoW целиком:

1: /// <summary> 2: /// Main class Unit of Work for Museum of Humor 3: /// </summary> 4: publicclass MuseumUoW : UnitOfWorkBase, IMuseumUoW, IDisposable { 5:   6: public MuseumUoW(IRepositoryProvider provider) 7: : base(provider) { 8: InitializeDbContext(); 9: } 10:   11:   12:   13: private MuseumContext DbContext; 14:   15: #region IDisposable 16:   17: publicvoid Dispose() { 18: Dispose(true); 19: GC.SuppressFinalize(this); 20: } 21:   22: protectedvirtualvoid Dispose(bool disposing) { 23: if (disposing) { 24: if (DbContext != null) { 25: DbContext.Dispose(); 26: } 27: } 28: } 29:   30: #endregion 31:   32:   33: #region Репозитории 34:   35: public IRepository<Exhibit> Exhibits { 36: get { return GetRepository<Exhibit>(); } 37: } 38: public IRepository<Hall> Halls { 39: get { return GetRepository<Hall>(); } 40: } 41: public IRepository<LinkItem> LinkItems { 42: get { return GetRepository<LinkItem>(); } 43: } 44: public IRepository<LinkCatalog> LinkCatalogs { 45: get { return GetRepository<LinkCatalog>(); } 46: } 47: public ISubscriberRepository Subscribers { 48: get { return GetRepositoryExt<ISubscriberRepository>(); } 49: } 50: public ILentaRepository Lentas { 51: get { return GetRepositoryExt<ILentaRepository>(); } 52: } 53: public ILogRepository Logs { 54: get { return GetRepositoryExt<ILogRepository>(); } 55: } 56: public ITagRepository Tags { 57: get { return GetRepositoryExt<ITagRepository>(); } 58: } 59:   60: #endregion 61:   62: publicoverridevoid InitializeDbContext() { 63: DbContext = new MuseumContext(); 64:   65: // запретим создавать proxy-классы для сущностей 66: // чтобы избежать проблем при сериализации объектов 67: DbContext.Configuration.ProxyCreationEnabled = false; 68:   69: // Отключим неявную «ленивую» загрузку 70: // (избежим проблемы с сериализацией) 71: DbContext.Configuration.LazyLoadingEnabled = false; 72:   73: // Отключим для повышения увеличения производительности 74: // валидацию при записи в базу данных. 75: // Я ее отключил, потому что уверен в том, что 76: // реализую валидацию объектов на форме (представления), 77: // а при реализации Web API я буду использовать валидацию 78: // для Knockout.Validation 79: DbContext.Configuration.ValidateOnSaveEnabled = false; 80:   81: // Инициализируем провайдера репозиториев 82: InitializeProvider(this.DbContext); 83: } 84:   85: publicoverridevoid Commit() { 86: DbContext.SaveChanges(); 87: } 88: }

Регистрируем в контейнере всего три типа:

1: container.RegisterInstance<RepositoryFactories>(new RepositoryFactories(data)); 2: container.RegisterType<IMuseumUoW, MuseumUoW>(); 3: container.RegisterType<IRepositoryProvider, RepositoryProvider>();

Или нет, давайте я снова приведу код (нового) Bootstrapper’а целиком, потому что надо показать как регистрируются типы для фабрики репозиториев:

1: publicstaticclass Bootstrapper { 2: 3: publicstaticvoid Initialise() { 4: var container = BuildUnityContainer(); 5: 6: DependencyResolver.SetResolver(new UnityDependencyResolver(container)); 7: GlobalConfiguration.Configuration.DependencyResolver =                      
                             new Unity.WebApi.UnityDependencyResolver(container); 8: } 9: 10: privatestatic IUnityContainer BuildUnityContainer() { 11: var container = new UnityContainer(); 12: var data = new Dictionary<Type, Func<DbContext, object>>{ 13: {typeof(IExhibitRepository), dbContext => new ExhibitRepository(dbContext)}, 14: {typeof(IHallRepository), dbContext => new HallRepository(dbContext)}, 15: {typeof(ILentaRepository), dbContext => new LentaRepository(dbContext)}, 16: {typeof(ILinkItemRepository), dbContext => new LinkItemRepository(dbContext)}, 17: {typeof(ILogRepository), dbContext => new LogRepository(dbContext)}, 18: {typeof(ISubscriberRepository), dbContext => new SubscriberRepository(dbContext)}, 19: {typeof(ILinkCatalogRepository), dbContext => new LinkCatalogRepository(dbContext)}, 20: {typeof(ITagRepository), dbContext => new TagRepository(dbContext)} 21: }; 22: 23: container.RegisterInstance<RepositoryFactories>(new RepositoryFactories(data)); 24: container.RegisterType<IMuseumUoW, MuseumUoW>(); 25: container.RegisterType<IRepositoryProvider, RepositoryProvider>(); 26: 27: return container; 28: } 29: }

Разберем немного последний листинг. В строке 12 создаем словарь (Dictionary) типов для разрешения (resolve). В строках с 13 по 20 перечисляем репозитории используемые в моем Unit of Work (MuseumUow). В строке 23 в фабрику репозиториев в конструктор “подставляем” созданный словарь типов.

Так как IMuseumUoW и IRepositoryProvider используются для вливаний (DI), практически везде, то также регистрируем их в контейнере в строке 24 и 25 соответственно.

Ссылки

Пример реализации на сайте asp.net

Заключение

В качестве заключения хочется сказать, что именно IMuseumUoW в дальнейшем будет использован (как вливание Dependency Injection) при разработке WEB API сервиса (ApiController). А потом (когда-нибудь… если вдруг не будет конца света), WEB API будет использован для написания программы для Windows Phone и потом еще для одной программы для Windows 8 для размещения в Windows Store.

Подробнее: http://feedproxy.google.com/~r/blogmusor/~3/PhrTL2naovM/101

Источник: lred.ru

Оцените статью
новости для мужчин