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


Поделиться статьей:
Акция: Закажи любой сайт до окончания акции и получи скидку + подбор семантического ядра + поисковую оптимизацию сайта Это позволит Вам получать еще больше трафика и соответственно клиентов из Интернета!
До конца акции осталось
0
5
4
3
2
1
0
0
9
8
7
6
5
4
3
2
1
0
0
5
4
3
2
1
0
0
9
8
7
6
5
4
3
2
1
0
0
9
8
7
6
5
4
3
2
1
0
0
9
8
7
6
5
4
3
2
1
0
|
Узнайте подробности акции у менеджеров компании по телефонам: 8-924-200-7194 г.Хабаровск 8-800-550-9899 Бесплатно по России (с 2:30 до 11:30 по Мск) |
Новые статьи и публикации
- 2021-02-24 » Как попасть в Clubhouse и что там делать: гид по новой соцсети
- 2021-02-24 » ТОП-10 онлайн-консультантов для сайта
- 2021-02-16 » UX-копирайтинг: как небольшие тексты существенно повышают продажи
- 2021-02-08 » Яндекс.Кью: особенности сервиса и его польза для бизнеса
- 2021-02-01 » Покупательское поведение пользователей: как оно изменилось за последнее время?
- 2021-01-22 » PHP и регулярные выражения: азы для новичков
- 2021-01-22 » Факторы ранжирования-2021
- 2021-01-19 » Вопросы юзабилити: на что обратить внимание в 2021 году?
- 2021-01-12 » Алгоритм YATI: новая разработка Яндекса
- 2020-12-29 » DdoS-атаки: суть и способы защиты от них
- 2020-12-21 » Оплата за конверсии в Яндекс.Директе: в чём выгода и как проходит подключение?
- 2020-12-14 » Предновогодняя суета: как привлечь клиентов и активизировать продажи?
- 2020-12-03 » Ведение блога: на своём сайте или на специальной платформе?
- 2020-11-30 » Виджеты для веб-сайтов: влияние кнопок на продажи
- 2020-11-30 » CRM: что это и как помогает бизнесу?
- 2020-11-13 » Тепловые карты сайта: особенности и возможности для продвижения
- 2020-11-11 » Яндекс.Клиенты: всё о новых возможностях георекламы
- 2020-10-27 » Описания товаров для интернет-магазинов: секреты успешных продаж
- 2020-10-27 » Увеличиваем мобильный трафик: 10 способов привлечь новых клиентов
- 2020-10-14 » Определение CMS сайта: как это сделать?
- 2020-10-13 » Сайты-агрегаторы: сотрудничать или бороться за ТОП?
- 2020-10-05 » Интернет-магазин в Instagram: как его открыть?
- 2020-09-28 » Интернет-тролли: как с ними бороться и не подмочить репутацию?
- 2020-09-17 » Bing: обновление для веб-мастеров
- 2020-09-15 » Подключение платформы Яндекс.Диалоги на веб-сайт
- 2020-09-07 » Яндекс.Вордстат: как это работает?
- 2020-08-27 » Настройка аудиорекламы в социальных сетях: как её выполнить?
- 2020-08-21 » Текст для карточки товара: как сделать идеальное описание?
- 2020-08-17 » Сайты-визитки: какими они должны быть?
- 2020-08-10 » Оценка страниц Google: как это работает?
Предоставляем полный комплекс услуг по созданию, обслуживанию и продвижению сайтов по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.