Как упростить доступ к данным с помощью EF
11 марта 2023 г.Реализация шаблона репозитория с помощью Entity Framework
Шаблон архитектуры репозитория часто используется при разработке программного обеспечения для отделения бизнес-логики приложения от уровня доступа к данным. Это упрощает модульное тестирование бизнес-логики приложения и приводит к созданию более чистой и управляемой кодовой базы.
Предпосылки
- Базовые знания концепций ООП.
* Любое знание языков программирования.
Итак, для начала C# и Entity Framework:
Начните с Миграция EF Core: пошаговое руководство
Цели обучения
- Как реализовать шаблон репозитория с помощью Entity Framework
* Подробнее о разделении проблем
* Как это обеспечивает тестируемость кода
* Как это обеспечивает возможность повторного использования кода
Начало работы
Инструмент объектно-реляционного сопоставления (ORM) Entity Framework для приложений .NET предлагает простой метод взаимодействия с базами данных. В статье показано, как использовать Entity Framework для создания шаблона репозитория.
Создать интерфейс репозитория
Создание интерфейса, определяющего методы взаимодействия с данными, – это первый шаг в реализации проекта репозитория. Ниже приведен пример интерфейса.
public interface IRepository<TEntity> where TEntity : class
{
TEntity Get(int id);
IEnumerable<TEntity> GetAll();
IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);
void Add(TEntity entity);
void AddRange(IEnumerable<TEntity> entities);
void Remove(TEntity entity);
void RemoveRange(IEnumerable<TEntity> entities);
}
Приведенный выше интерфейс определяет стандартные операции CRUD (создание, чтение, обновление, удаление), которые обычно используются при работе с данными.
Создайте реализацию репозитория
Следующим шагом будет практическая реализация интерфейса IRepository. Ниже вы найдете иллюстрацию реализации.
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly DbContext _context;
private readonly DbSet<TEntity> _dbSet;
public Repository(DbContext context)
{
_context = context;
_dbSet = context.Set<TEntity>();
}
public TEntity Get(int id)
{
return _dbSet.Find(id);
}
public IEnumerable<TEntity> GetAll()
{
return _dbSet.ToList();
}
public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
{
return _dbSet.Where(predicate);
}
public void Add(TEntity entity)
{
_dbSet.Add(entity);
}
public void AddRange(IEnumerable<TEntity> entities)
{
_dbSet.AddRange(entities);
}
public void Remove(TEntity entity)
{
_dbSet.Remove(entity);
}
public void RemoveRange(IEnumerable<TEntity> entities)
{
_dbSet.RemoveRange(entities);
}
}
Вышеупомянутый класс Repository реализует интерфейс IRepository и обеспечивает реальную реализацию каждого метода.
Используйте репозиторий в своем приложении
Создайте экземпляр репозитория и передайте его DbContext, который намеревается использовать его в приложении. Ниже вы найдете иллюстрацию того, как репозиторий можно использовать в консольном приложении:
static void Main(string[] args)
{
using (var context = new MyDbContext())
{
var repository = new Repository<Customer>(context);
var customer = new Customer { Name = "John Doe", Email = "john.doe@example.com" };
repository.Add(customer);
var customers = repository.GetAll();
foreach (var c in customers)
{
Console.WriteLine(c.Name);
}
}
}
На приведенном выше рисунке мы создали экземпляр Repository для объекта Customer и предоставили нужный DbContext.
Зачем использовать шаблон репозитория вместе с EF?
Реализация шаблона репозитория с помощью Entity Framework имеет несколько преимуществ:
Разделение проблем
Шаблон репозитория помогает вашему коду стать более модульным и простым в обслуживании за счет отделения бизнес-логики от логики доступа к данным.
public interface IRepository<T> where T : class
{
IEnumerable<T> GetAll();
T GetById(int id);
void Add(T entity);
void Update(T entity);
void Delete(T entity);
}
public class Repository<T> : IRepository<T> where T : class
{
private readonly DbContext _context;
private readonly DbSet<T> _entities;
public Repository(DbContext context)
{
_context = context;
_entities = context.Set<T>();
}
public IEnumerable<T> GetAll()
{
return _entities.ToList();
}
public T GetById(int id)
{
return _entities.Find(id);
}
public void Add(T entity)
{
_entities.Add(entity);
_context.SaveChanges();
}
public void Update(T entity)
{
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
}
public void Delete(T entity)
{
_entities.Remove(entity);
_context.SaveChanges();
}
}
В приведенном выше примере мы установили интерфейс IRepository и шаблон практической реализации Repository, чтобы отличить логику доступа к данным от бизнес-логики.
Теперь бизнес-логика нашего приложения может взаимодействовать с уровнем доступа к данным через интерфейс IRepository, абстрагируя, как данные сохраняются или извлекаются.
Тестируемость
Шаблоны репозитория упрощают разработку модульных тестов для бизнес-логики вашего приложения, не беспокоясь о базе данных.
Рассмотрим следующий пример класса службы, который зависит от упомянутого выше репозитория:
public class ProductService
{
private readonly IRepository<Product> _repository;
public ProductService(IRepository<Product> repository)
{
_repository = repository;
}
public IEnumerable<Product> GetAllProducts()
{
return _repository.GetAll();
}
public Product GetProductById(int id)
{
return _repository.GetById(id);
}
public void AddProduct(Product product)
{
_repository.Add(product);
}
public void UpdateProduct(Product product)
{
_repository.Update(product);
}
public void DeleteProduct(Product product)
{
_repository.Delete(product);
}
}
Чтобы протестировать класс ProductService
, нам нужно реализовать фиктивный объект репозитория, реализующий интерфейс IRepository<Product>
. Ниже приведен пример кода для того же самого.
[TestClass]
public class ProductServiceTests
{
[TestMethod]
public void GetAllProducts_Returns_All_Products()
{
// Arrange
var products = new List<Product>
{
new Product { Id = 1, Name = "Product 1", Price = 10.0m },
new Product { Id = 2, Name = "Product 2", Price = 20.0m },
new Product { Id = 3, Name = "Product 3", Price = 30.0m }
};
var mockRepository = new Mock<IRepository<Product>>();
mockRepository.Setup(r => r.GetAll()).Returns(products.AsQueryable());
var productService = new ProductService(mockRepository.Object);
// Act
var result = productService.GetAllProducts();
// Assert
CollectionAssert.AreEqual(products, result.ToList());
}
// Other unit tests for the ProductService class...
}
Повторное использование кода
Дизайн репозитория поощряет повторное использование кода, поскольку позволяет повторно использовать одну и ту же логику доступа к данным во многих компонентах приложения.
Давайте представим, что наше приложение содержит две сущности: Customer и Order. Наряду с получением клиента по идентификатору и всех заказов клиента, мы также хотим восстановить всех клиентов и все заказы.
Для управления этими типичными операциями CRUD мы можем разработать универсальный интерфейс репозитория IRepository<TEntity>
и общую реализацию репозитория Repository<TEntity>
.
Затем разработайте настраиваемые интерфейсы репозитория и реализации для наших сущностей «Клиент» и «Заказ».
Теперь давайте создадим определенные интерфейсы репозитория и реализации для сущностей Customer
и Order
:
public interface ICustomerRepository : IRepository<Customer>
{
Customer GetCustomerById(int id);
}
public class CustomerRepository : Repository<Customer>, ICustomerRepository
{
public CustomerRepository(DbContext dbContext) : base(dbContext)
{
}
public Customer GetCustomerById(int id)
{
return GetById(id);
}
}
public interface IOrderRepository : IRepository<Order>
{
IQueryable<Order> GetOrdersByCustomer(int customerId);
}
public class OrderRepository : Repository<Order>, IOrderRepository
{
public OrderRepository(DbContext dbContext) : base(dbContext)
{
}
public IQueryable<Order> GetOrdersByCustomer(int customerId)
{
return GetAll().Where(o => o.CustomerId == customerId);
}
}
Повторно используя класс Repository<TEntity>
для классов CustomerRepository
и OrderRepository
, мы наследуем от IRepository<TEntity>< /code> Интерфейс для обеспечения общих операций CRUD.
Это позволит избежать дублирования кода и сократить шаблонный код, необходимый для каждой реализации репозитория.
Гибкость
Дизайн репозитория изменяет базовую систему хранения данных, не влияя на бизнес-логику вашего приложения. Вы можете переключиться с сервера SQL на базу данных NoSQL, не ограничивая бизнес-логику.
Заключение
Шаблон репозитория может создать более чистую и удобную в сопровождении кодовую базу и упростить модульное тестирование бизнес-логики. Основное внимание уделяется разработке более масштабируемого и гибкого приложения путем изоляции логики доступа к данным от бизнес-логики.
Подробнее о шаблонах проектирования
Единица работы в C#: практическое руководство для разработчиков
Подпишитесь на меня
Публикация C#, LinkedIn, Instagram, Twitter, Dev.to
Также опубликовано здесь
Оригинал