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-04-22 » Комментирование кода и генерация документации в PHP
- 2024-04-22 » SEO в России и на Западе: в чем основные отличия
- 2024-04-22 » SEO для международного масштабирования
- 2024-04-22 » Как использовать XML-карты для продвижения сайта
- 2024-04-22 » Цифровой маркетинг: инструменты для продвижения и рекламы в 2024 году
- 2024-04-22 » Что такое CSS-модули и зачем они нам?
- 2024-04-17 » 23 сервиса для эффективного экспресс-аудита любого сайта
- 2024-04-08 » Яндекс переходит на новую версию Wordstat
- 2024-04-08 » Яндекс интегрировал в свой облачный сервис эмпатичную нейросеть
- 2024-04-08 » Новая версия нейросети Claude превзошла по мощности аналоги Google и OpenAI
- 2024-04-08 » Как пользоваться GPT 4 и Claude бесплатно и без VPN
- 2024-03-13 » Стратегии SEO на 2024 год
- 2024-03-13 » Как использовать анимацию с помощью JavaScript-библиотеки GSAP
- 2024-03-13 » Использование GSAP 3 для веб-анимации
- 2024-03-13 » Cогласование топографической съёмки с эксплуатирующими организациями
- 2024-02-19 » Теряются лиды? Как настроить сквозную аналитику
- 2024-02-17 » Мерч и IT: на что обратить внимание в 2024 году
- 2024-02-16 » Копируем с RSync: основные примеры синхронизации файлов
- 2024-02-15 » Лучшие noCode AI платформы для создания диалоговых ботов
- 2024-02-14 » Факторы ранжирования Google 2024 — исследование Semrush
- 2024-02-12 » Перенос сайта на другой хостинг
- 2024-02-05 » В России сформирован реестр хостинг-провайдеров
- 2024-02-04 » Использование SSH для подключения к удаленному серверу Ubuntu
- 2024-02-03 » Подключаемся к серверу за NAT при помощи туннеля SSH. Простая и понятная инструкция
- 2024-02-02 » Настройка CI/CD для Gitlab-репозитория: схемы и гайд по шагам
- 2024-02-01 » GitLab CI Pipeline. Запуск сценария через SSH на удаленном сервере
- 2024-01-29 » Introduction to GitLab’s CI/CD for Continuous Deployments
- 2024-01-26 » Настройка GitLab CI/CD
- 2024-01-25 » Установка shell gitlab runner
- 2024-01-25 » Установка и регистрация gitlab-runner в docker контейнере
Самый лучший человек тот, который живет преимущественно своими мыслями и чужими чувствами, самый худший сорт человека - который живет чужими мыслями и своими чувствами. Из различных сочетаний этих четырех основ, мотивов деятельности - все различие людей. Люди, живущие только своими чувствами, - это звери. Толстой Лев Николаевич - (1828-1910) - великий русский писатель. Его творчество оказало огромное влияние на мировую литературу |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.