Как упростить доступ к данным с помощью EF

Как упростить доступ к данным с помощью EF

11 марта 2023 г.

Реализация шаблона репозитория с помощью Entity Framework

Шаблон архитектуры репозитория часто используется при разработке программного обеспечения для отделения бизнес-логики приложения от уровня доступа к данным. Это упрощает модульное тестирование бизнес-логики приложения и приводит к созданию более чистой и управляемой кодовой базы.

Предпосылки

  • Базовые знания концепций ООП.

* Любое знание языков программирования.

Итак, для начала C# и Entity Framework:

Введение в C#

Начните с Миграция 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


Также опубликовано здесь


Оригинал