В большинстве бизнес-приложений вы будете хранить и извлекать данные из хранилища данных, выполняя операции CRUD (создание, чтение, обновление и удаление). В связи с этим существует несколько технологий и инструментов, которые вы можете использовать. Например, вы можете выбрать одну из доступных платформ ORM, таких как Entity Framework, Dapper или NHibernate.
Однако проблемы выходят за рамки хранения и извлечения данных. Вы хотите использовать инструмент или подход, который поможет вам написать повторно используемый, поддерживаемый и гибкий код. Для этого вы можете использовать шаблоны проектирования, такие как репозиторий и шаблоны единиц работы.
Мы рассмотрели шаблон проектирования репозитория в более ранней статье здесь. В этой статье мы рассмотрим шаблон проектирования единиц работы с соответствующими примерами кода, чтобы проиллюстрировать рассмотренные концепции.
Чтобы использовать примеры кода, представленные в этой статье, в вашей системе должна быть установлена Visual Studio 2022. Если у вас еще нет копии, вы можете скачать Visual Studio 2022 здесь.
Создайте проект веб-API ASP.NET Core 7 в Visual Studio 2022.
Прежде всего, давайте создадим проект ASP.NET Core 7 в Visual Studio 2022. Выполните следующие действия:
- Запустите интегрированную среду разработки Visual Studio 2022.
- Нажмите «Создать новый проект».
- В окне «Создать новый проект» выберите «ASP.NET Core Web API» из списка отображаемых шаблонов.
- Нажмите “Далее.
- В окне «Настроить новый проект» укажите имя и расположение нового проекта.
- При желании установите флажок «Поместить решение и проект в один каталог», в зависимости от ваших предпочтений.
- Нажмите “Далее.
- В окне «Дополнительная информация» оставьте флажок «Использовать контроллеры (снимите флажок, чтобы использовать минимальные API)», поскольку в этом примере мы не будем использовать минимальные API. Оставьте «Тип аутентификации» как «Нет» (по умолчанию).
- Убедитесь, что флажки «Включить поддержку Open API», «Настроить для HTTPS» и «Включить Docker» сняты, поскольку мы не будем использовать эти функции здесь.
- Щелкните Создать.
Мы будем использовать этот проект веб-API ASP.NET Core 7 для работы с шаблоном проектирования единиц работы в следующих разделах.
Что такое шаблон проектирования единиц работы?
Шаблон проектирования единиц работы гарантирует целостность и непротиворечивость данных в приложениях. Это гарантирует, что все изменения, внесенные в несколько объектов в приложении, будут зафиксированы в базе данных или отменены. Он обеспечивает организованный и последовательный способ управления изменениями и транзакциями базы данных.
Основная цель этого шаблона проектирования — поддерживать согласованность и атомарность, гарантируя, что все изменения, внесенные в базу данных, будут успешными или не удастся, если возникнет проблема. В результате поддерживается согласованность данных, а изменения в базе данных всегда точны. Используя шаблон проектирования единиц работы, разработчики могут сэкономить время и силы, сосредоточившись на бизнес-логике, а не на доступе к базе данных и управлении ею.
Реализация шаблона проектирования единицы работы на C#
Чтобы реализовать шаблон проектирования единицы работы, вы должны указать операции, которые поддерживает единица работы, в интерфейсе или контракте, а затем реализовать эти методы в конкретном классе. Вы можете воспользоваться преимуществами любой технологии доступа к данным, например Entity Framework, Dapper или ADO.NET.
Типичная единица реализации работы будет включать компоненты, перечисленные ниже:
- Интерфейс для единицы работы. Операции, которые будет выполнять единица работы, определяются контрактом или интерфейсом, содержащим объявление всех методов добавления, изменения и удаления данных, а также фиксации или отката изменений в базе данных.
- Конкретная реализация интерфейса единицы работы. Конкретная реализация интерфейса, который мы только что описали. Эта реализация будет включать в себя все операции, выполняющие какие-либо транзакции или операции с данными, а также операции по откату или фиксации изменений.
- Класс репозитория и его интерфейс. Код, необходимый для доступа или изменения данных, содержится в классе репозитория и его интерфейсе. Другими словами, у вас должен быть класс репозитория, который использует каждый из методов интерфейса, и интерфейс для вашего репозитория, определяющий разрешенные действия.
Итак, приступим!
Создайте класс CustomDbContext
В Entity Framework или Entity Framework Core DbContext представляет собой шлюз к базе данных. Ниже приведен класс DbContext, который может быть создан автоматически при использовании Entity Framework Core. Мы будем использовать этот класс несколько раз в следующих фрагментах кода.
public class CustomDbContext : DbContext { public CustomDbContext(DbContextOptions<CustomDbContext> options) : base(options) { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { } public DbSet<Author> Authors { get; set; } }
Определить интерфейс для единицы работы
Создайте новый файл .cs с именем IUnitOfWork.cs в своем проекте и введите следующий код.
public interface IUnitOfWork : IDisposable { void Commit(); void Rollback(); IRepository<T> Repository<T>() where T : class; }
Реализовать конкретный класс единицы работы
Затем вы должны создать конкретную реализацию интерфейса IUnitOfWork. Для этого создайте новый класс с именем UnitOfWork в файле с таким же именем и расширением .cs и введите следующий код.
public class UnitOfWork : IUnitOfWork, IDisposable { private readonly DbContext _dbContext; private bool _disposed; public UnitOfWork(DbContext dbContext) { _dbContext = dbContext; } public void Commit() { _dbContext.SaveChanges(); } public void Rollback() { foreach (var entry in _dbContext.ChangeTracker.Entries()) { switch (entry.State) { case EntityState.Added: entry.State = EntityState.Detached; break; } } } public IRepository<T> Repository<T>() where T : class { return new Repository<T>(_dbContext); } private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { _dbContext.Dispose(); } } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } }
Определить интерфейс репозитория
Шаблон проектирования репозитория упрощает доступ к данным, абстрагируя логику от других компонентов и модулей приложения. Ваши объекты будут сохраняться без необходимости напрямую работать с логикой, используемой для доступа к базовой базе данных. Другими словами, репозиторий инкапсулирует логику, используемую для доступа к источнику данных.
Следующим шагом является определение интерфейса для вашего репозитория. Этот интерфейс должен предоставлять объявления методов, поддерживаемых репозиторием.
Создайте новый интерфейс с именем IRepository в файле с именем IRepository.cs и замените созданный по умолчанию код следующим кодом.
public interface IRepository<T> where T : class { T GetById(object id); IList<T> GetAll(); void Add(T entity); }
Реализовать интерфейс репозитория
Наконец, вы должны реализовать интерфейс репозитория, который мы только что создали. Для этого создайте новый файл с именем Repository.cs и замените сгенерированный по умолчанию код следующим кодом.
public class Repository<T> : IRepository<T> where T : class { private readonly CustomDbContext _dbContext; private readonly DbSet<T> _dbSet; public T GetById(object id) { return _dbSet.Find(id); } public Repository(DbContext dbContext) { _dbContext = dbContext; _dbSet = _dbContext.Set<T>(); } public IList<T> GetAll() { return _dbSet.ToList(); } public void Add(T entity) { _dbSet.Add(entity); } }
Зарегистрируйте DbContext
В качестве бонуса вы должны зарегистрировать DbContext как службу в файле Program.cs, чтобы вы могли воспользоваться внедрением зависимостей для извлечения экземпляров DbContext в классах Repository и UnitOfWork.
Это можно сделать двумя способами: с помощью метода AddDbContext или с помощью метода AddDbContextPool. Метод AddDbContextPool предпочтительнее, поскольку использование пула экземпляров поможет вашему приложению масштабироваться.
builder.Services.AddDbContextPool<CustomDbContext>(o=>o.UseSqlServer("Specify the database connection string here..."));
В этой статье мы создали универсальную реализацию репозитория, которая использует интерфейс IRepository и класс Repository. Вы можете создать репозиторий для определенного объекта, создав класс, реализующий универсальный интерфейс IRepository
public class AuthorRepository : IRepository<Author> { //Write code here to implement the members of the IRepository interface }
Репозитории и шаблон проектирования единиц работы создают уровень абстракции между вашей бизнес-логикой и уровнем доступа к данным, значительно облегчая жизнь разработчику. Шаблон единицы работы использует одну транзакцию или одну единицу работы для нескольких операций вставки, обновления и удаления. Эти операции либо успешны, либо терпят неудачу как единое целое. Другими словами, все операции будут зафиксированы как одна транзакция или отменены как единое целое.
В то время как шаблон единицы работы используется для объединения нескольких операций в одной транзакции, репозитории представляют собой типы, которые инкапсулируют логику доступа к данным, отделяя эту задачу от вашего приложения.