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
Дайджест новых статей по интернет-маркетингу на ваш email
Новые статьи и публикации
- 2024-12-26 » Универсальный промпт для нейросети: как выжать максимум из ChatGPT, YandexGPT, Gemini, Claude в 2025
- 2024-11-26 » Капитан грузового судна, или Как начать использовать Docker в своих проектах
- 2024-11-26 » Обеспечение безопасности ваших веб-приложений с помощью PHP OOP и PDO
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-15 » Перенос сайта на WordPress с одного домена на другой
- 2024-11-08 » OSPanel 6: быстрый старт
- 2024-11-08 » Как установить PhpMyAdmin в Open Server Panel
- 2024-09-30 » Как быстро запустить Laravel на Windows
- 2024-09-25 » Next.js
- 2024-09-05 » OpenAI рассказал, как запретить ChatGPT использовать содержимое сайта для обучения
- 2024-08-28 » Чек-лист: как увеличить конверсию интернет-магазина на примере спортпита
- 2024-08-01 » WebSocket
- 2024-07-26 » Интеграция с Яндекс Еда
- 2024-07-26 » Интеграция с Эквайринг
- 2024-07-26 » Интеграция с СДЕК
- 2024-07-26 » Интеграция с Битрикс-24
- 2024-07-26 » Интеграция с Travelline
- 2024-07-26 » Интеграция с Iiko
- 2024-07-26 » Интеграция с Delivery Club
- 2024-07-26 » Интеграция с CRM
- 2024-07-26 » Интеграция с 1C-Бухгалтерия
- 2024-07-24 » Что такое сторителлинг: техники и примеры
- 2024-07-17 » Ошибка 404: что это такое и как ее использовать для бизнеса
- 2024-07-03 » Размещайте прайс-листы на FarPost.ru и продавайте товары быстро и выгодно
- 2024-07-01 » Профилирование кода в PHP
- 2024-06-28 » Изучаем ABC/XYZ-анализ: что это такое и какие решения с помощью него принимают
- 2024-06-17 » Зачем вам знать потребности клиента
- 2024-06-11 » Что нового в работе Яндекс Метрики: полный обзор обновления
- 2024-06-11 » Поведенческие факторы ранжирования в Яндексе
"Успеха в ближайшем десятилетии добьются только те компании, которые сумеют реорганизовать свою работу с помощью электронного инструментария. Только это позволит им быстро принимать правильные решения, предпринимать эффективные действия и поддерживать тесные плодотворные связи со своими клиентами." |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.