Нарушается ли шаблон репозитория при использовании IQueryable вне его?

Я пишу серверное приложение API с использованием .NET Core и Visual Studio.

Вот структура решения:

[ProjectName] — Решение

  • [ProjectName].API — Проект API, содержащий контроллеры, DTO, AutoMapper и т. Д.
  • [ProjectName].Core — Проект библиотеки классов, содержащий сервисы и бизнес-логику
  • [ProjectName].Data — Проект библиотеки классов, содержащий DbContext и Migrations
  • [ProjectName].Domain — Проект библиотеки классов, содержащий объекты базы данных
  • [ProjectName].Interfaces — Проект библиотеки классов, содержащий интерфейсы
  • [ProjectName].Repositories — Проект библиотеки классов, содержащий репозитории
  • [ProjectName].Tests — Консольное приложение, содержащее модульные тесты

Вот структура кода:

В .Repositories проект есть BaseRepository:

public class BaseRepository<T, TPrimaryKey> : IBaseRepository<T, TPrimaryKey> where T : class where TPrimaryKey : struct
{
    private readonly DatabaseContext _dbContext;

    public BaseRepository(DatabaseContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<IEnumerable<T>> GetAll()
    {
        return await _dbContext.Set<T>().ToListAsync();
    }

    public IQueryable<T> GetQueryable()
    {
        return _dbContext.Set<T>();
    }

    public async Task<T> Find(TPrimaryKey id)
    {
        return await _dbContext.Set<T>().FindAsync(id);
    }

    public async Task<T> Add(T entity, bool saveChanges = true)
    {
        await _dbContext.Set<T>().AddAsync(entity);
        if (saveChanges) 
            await _dbContext.SaveChangesAsync();

        return await Task.FromResult(entity);
    }

    public async Task Edit(T entity, bool saveChanges = true)
    {
        _dbContext.Entry(entity).State = EntityState.Modified;
        if (saveChanges) 
            await _dbContext.SaveChangesAsync();
    }

    public async Task Delete(T entity, bool saveChanges = true)
    {
        if (entity == null)
            throw new NullReferenceException();

        _dbContext.Set<T>().Remove(entity);
        if (saveChanges) 
            await _dbContext.SaveChangesAsync();
    }

    public async Task<IEnumerable<T>> BulkInsert(IEnumerable<T> entities, bool saveChanges = true)
    {
        foreach (T entity in entities)
        {
            await _dbContext.Set<T>().AddAsync(entity);
        }

        if (saveChanges) 
            await _dbContext.SaveChangesAsync();

        return await Task.FromResult(entities);
    }

    public async Task BulkUpdate(IEnumerable<T> entities, bool saveChanges = true)
    {
        foreach (T entity in entities)
        {
            _dbContext.Entry(entity).State = EntityState.Modified;
        }

        if (saveChanges) 
            await _dbContext.SaveChangesAsync();
    }

    public async Task Save()
    {
        await _dbContext.SaveChangesAsync();
    }
}

В .Interfaces проект есть:

IBaseRepository:

public interface IBaseRepository<T, E> where T : class where E : struct
{
    Task<IEnumerable<T>> GetAll();
    IQueryable<T> GetQueryable();
    Task<T> Find(E id);
    Task<T> Add(T entity, bool saveChanges = true);
    Task Edit(T entity, bool saveChanges = true);
    Task Delete(T entity, bool saveChanges = true);
    Task<IEnumerable<T>> BulkInsert(IEnumerable<T> entities, bool saveC
    Task BulkUpdate(IEnumerable<T> entities, bool saveChanges = true);
    Task Save();
}

IServiceBase:

public interface IServiceBase<TEntity, TPrimaryKey>
{
    Task<TEntity> GetById(TPrimaryKey id);
    Task<TEntity> GetSingle(Expression<Func<TEntity, bool>> whereCondition);
    Task<IEnumerable<TEntity>> GetAll();
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> whereCondition);
    IQueryable<TEntity> GetAllQueryable();
    IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> whereCondition);
    Task<TEntity> Create(TEntity entity);
    Task Delete(TEntity entity);
    Task Update(TEntity entity);
    Task<long> Count(Expression<Func<TEntity, bool>> whereCondition);
    Task<long> Count();
    Task<IEnumerable<TEntity>> BulkInsert(IEnumerable<TEntity> entities);
    Task BulkUpdate(IEnumerable<TEntity> entities);
}

и все интерфейсы сервисов конкретных сущностей:

Например IAddressServices:

public interface IAddressService : IServiceBase<Address, Guid>
{
    Task<Address> VerifyAddress(Address address);
}

В .Core проект есть:

ServiceBase:

public abstract class ServiceBase<TEntity, TRepository, TPrimaryKey> : IServiceBase<TEntity, TPrimaryKey>
        where TEntity : class
        where TPrimaryKey : struct
        where TRepository : IBaseRepository<TEntity, TPrimaryKey>
{
    public TRepository Repository;

    public ServiceBase(IBaseRepository<TEntity, TPrimaryKey> rep)
    {
        Repository = (TRepository)rep;
    }

    public virtual async Task<TEntity> GetById(TPrimaryKey id)
    {
        return await Repository.Find(id);
    }

    public async Task<TEntity> GetSingle(Expression<Func<TEntity, bool>> whereCondition)
    {
        return await Repository.GetQueryable().Where(whereCondition).FirstOrDefaultAsync();
    }

    public async Task<IEnumerable<TEntity>> GetAll()
    {
        return await Repository.GetAll();
    }

    public IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> whereCondition)
    {
        return Repository.GetQueryable().Where(whereCondition);
    }

    public IQueryable<TEntity> GetAllQueryable()
    {
        return Repository.GetQueryable();
    }

    public IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> whereCondition)
    {
        return Repository.GetQueryable().Where(whereCondition);
    }

    public virtual async Task<TEntity> Create(TEntity entity)
    {
        return await Repository.Add(entity);
    }

    public virtual async Task Delete(TEntity entity)
    {
        await Repository.Delete(entity);
    }

    public virtual async Task Update(TEntity entity)
    {
        await Repository.Edit(entity);
    }

    public async Task<long> Count(Expression<Func<TEntity, bool>> whereCondition)
    {
        return await Repository.GetQueryable().Where(whereCondition).CountAsync();
    }

    public async Task<long> Count()
    {
        return await Repository.GetQueryable().CountAsync();
    }       

    public async Task<IEnumerable<TEntity>> BulkInsert(IEnumerable<TEntity> entities)
    {
        return await Repository.BulkInsert(entities);
    }

    public async Task BulkUpdate(IEnumerable<TEntity> entities)
    {
        await Repository.BulkUpdate(entities);
    }
}

и конкретные реализации услуг:

Например AddressService:

public class AddressService : ServiceBase<Address, IBaseRepository<Address, Guid>, Guid>, IAddressService
{
    public AddressService(IBaseRepository<Address, Guid> rep) : base(rep)
    {
    }

    public async Task<Address> VerifyAddress(Address address)
    {
        //logic
    }
}

В .API В проекте есть контроллеры:

Например ProductController:

 public class ProductController : ControllerBase
    {
        private readonly IProductService _productService;
        private readonly IAddressService _addressService;
        private readonly ILogger _logger;
        private readonly IMapper _mapper;

        public ProductController (IProductService productService,
            IAddressService addressService,
            ILogger<ProductController> logger,
            IMapper mapper)
        {
            _packageService = packageService;
            _addressService = addressService;
            _logger = logger;
            _mapper = mapper;
        }

        [HttpGet]
        public async Task<IActionResult> GetAllProductsWithAddresses()
        {
            try
            {
                var products = await _productService.GetAllQueryable().Include(x => x.Address).ToListAsync();

                return Ok(_mapper.Map<List<ProductResponse>>(products));
            }
            catch (Exception e)
            {
                _logger.LogError($"An unexpected error occured: ${e}");
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }
    }

Я много читал о том, что шаблон репозитория иногда бывает плохим, и его следует использовать только в случае необходимости модульных тестов. В моем случае необходимы модульные тесты, поэтому я хочу убедиться, что использую его правильно, и в будущем не будет узких мест в производительности.

  1. Использует GetQueryable в ProductController исправить дизайн шаблона репозитория? Если нет, то это только нарушение шаблона репозитория или есть другие проблемы с производительностью?
  2. Если мне не нужны модульные тесты, лучше ли просто удалить уровень репозитория и использовать EF непосредственно в службах или контроллерах?
  3. Поскольку мне нужно было обновить несколько записей в базе данных, используя BulkUpdate из BaseRepository не идеален с точки зрения производительности, поскольку для обновления 1000 строк в базе данных будет выполнено 1000 отдельных запросов. В идеале было бы выполнить 1 необработанный SQL-запрос, используя dbContext.Database.ExecuteSqlCommand. Как и где я могу интегрировать выполнение необработанных SQL-запросов в такую ​​настройку проекта, чтобы убедиться, что чистая архитектура удовлетворяет требованиям?
  4. Если ответ на третий вопрос заключается в том, что вы не можете интегрировать необработанные SQL-запросы в эту архитектуру, чтобы сделать ее чистой, стоит ли мне посмотреть на сторонние библиотеки, которые включают эти функции, такие как Entity Framework plus (https://entityframework-plus.net/?z=ef-extended). Я не так хорошо знаком с этой библиотекой, но, как я выяснил, они выполняют 1 необработанный SQL-запрос при обновлении нескольких записей. Пример интеграции будет await GetAllQueryable().Where(p => p.Name.Contains("test")).UpdateAsync(p => new Product() { Quantity = 10 });. Лучше использовать сторонние библиотеки, такие как EF Plus, вместо сырых SQL-запросов?

Любая помощь приветствуется.

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *