Общий шаблон репозитория .Net Core Entity Framework — реализация общей службы с помощью UnitOfWork

Я хочу использовать Shared Service, который включает в себя некоторые общие методы, такие как создание, обновление, удаление на моем бизнес-уровне. Я уже реализовал репозиторий и рабочие классы, но столкнулся с некоторыми проблемами при попытке создать общий сервис. Давайте предположим, что у нас есть неразделяемый метод создания, подобный этому:

public class ProductService : IProductService
{
    private readonly IUnitOfWork _unitOfWork;
    public ProductService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
    public async Task<Product> CreateProduct(Product newProduct)
    {
        await _unitOfWork.Products.AddAsync(newProduct);
        await _unitOfWork.CommitAsync();
        return newProduct;
    }
}

Часть, которая меня смущает в приведенном выше коде, заключается в том, что я вызываю свой UnitOfWork с командой _unitOfWork.Product, как нам преобразовать его в unitOfWork.TEntity, чтобы сделать его универсальным? Является ли это возможным? В этом случае я попытался сделать общий, но я думаю, что нет такой вещи, как _unitOfWork.TEntity. Мне просто нужно отредактировать класс Service, я добавляю другие связанные классы, чтобы предоставить дополнительную информацию.

Услуга:

    public class Service<TEntity>: IService<TEntity> where TEntity : class
    {
        private readonly IUnitOfWork _unitOfWork;
        public Service(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }
        public async Task<TEntity> AddAsync(TEntity entity)
        {
            await _unitOfWork.TEntity.AddAsync(entity);
            await _unitOfWork.CommitAsync();
            return entity;
        }
    }

IService:

    public interface IService<TEntity> where TEntity : class
    {
        Task<TEntity> AddAsync(TEntity entity);
    }

Репозиторий:

    public abstract class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        private readonly DbContext _context;
        private readonly DbSet<TEntity> _dbSet;

        public Repository(ECommerceDbContext context)
        {
            _context = context;
            _dbSet = context.Set<TEntity>();
        }

        public async Task<TEntity> AddAsync(TEntity entity)
        {
            entity.CreateDate = DateTime.Now;
            await _dbSet.AddAsync(entity);
            await SaveAsync();
            return entity;
        }

        public async Task AddAsync(IEnumerable<TEntity> entities)
        {
            foreach (var item in entities)
            {
                await AddAsync(item);
            }
        }

        public async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> expression)
        {
            return await _dbSet.AnyAsync(expression);
        }

        public async Task<bool> AnyAsync()
        {
            return await AnyAsync(x => true);
        }

        public async Task<long> CountAsync()
        {
            return await CountAsync(x => true);
        }

        public async Task<long> CountAsync(Expression<Func<TEntity, bool>> expression)
        {
            return await _dbSet.LongCountAsync(expression);
        }

        public void Delete(TEntity model)
        {
            _dbSet.Remove(model);
        }

        public async Task DeleteAsync(int id)
        {
            var entity = await GetAsync(id);
            Delete(entity);
        }

        public async Task<TEntity> FirstOrDefault(Expression<Func<TEntity, bool>> expression)
        {
            return await _dbSet.FirstOrDefaultAsync(expression);
        }

        public async Task<TEntity> GetAsync(int id)
        {
            return await FirstOrDefault(x => x.Id == id);
        }

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

        public async Task SaveAsync()
        {
           await _context.SaveChangesAsync();
        }

        public async Task<TEntity> Update(TEntity entity)
        {
            var temp = GetAsync(entity.Id);
            entity.UpdateDate = DateTime.Now;
            _context.Entry(temp).CurrentValues.SetValues(entity);
            await SaveAsync();
            return await temp;
        }
    }

IРепозиторий:

    public interface IRepository<TEntity> where TEntity :class
    {
        Task<TEntity> AddAsync(TEntity entity);
        Task AddAsync(IEnumerable<TEntity> entities);
        Task<bool> AnyAsync(Expression<Func<TEntity, bool>> expression);
        Task<bool> AnyAsync();
        Task<long> CountAsync();
        Task<long> CountAsync(Expression<Func<TEntity, bool>> expression);
        void Delete(TEntity model);
        Task DeleteAsync(int id);
        Task<TEntity> FirstOrDefault(Expression<Func<TEntity, bool>> expression);
        Task<TEntity> GetAsync(int id);
        Task<IEnumerable<TEntity>> GetAll();
        Task SaveAsync();
        Task<TEntity> Update(TEntity entity);
    }

Единица Работы:

    public class UnitOfWork:IUnitOfWork
    {
        private readonly ECommerceDbContext _context;
        private ProductRepository _productRepository;

        public UnitOfWork(ECommerceDbContext context)
        {
            _context = context;
        }

        public IProductRepository Products => _productRepository = _productRepository ?? new ProductRepository(_context);

        public async Task<int> CommitAsync()
        {
            return await _context.SaveChangesAsync();
        }

        public void Dispose()
        {
            _context.Dispose();
        }
    }
IUnitOfWork

    public interface IUnitOfWork : IDisposable
    {
        IProductRepository Products { get; }
        Task<int> CommitAsync();
    }

Если универсальной реализации, отвечающей вашим потребностям, нет, вы всегда можете использовать шаблон обратного вызова action/func, чтобы применить обновление к экземпляру с заданной областью действия.

David L 23.12.2020 23:38

Могу ли я реализовать универсальный метод AddAsync, который может быть полезен для всех сущностей? Как я могу использовать шаблон обратного вызова action/func? Можете ли вы объяснить больше?

alihangir 24.12.2020 02:07

Я добавил ответ с примером, объясняющим подход.

David L 24.12.2020 03:16

Имейте в виду, что общий репозиторий считается анти-шаблоном.

Amit Joshi 22.01.2021 07:59
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
1 498
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Служба TEntity : class .. Я думаю, здесь класс TEntity : Entity.

Будет полезно, если вы поделитесь своими объектами IProductRepository и ProductRepository.

public interface IService<TEntity> where TEntity : Entity
{
    Task<TEntity> AddAsync(TEntity entity);
}

public class Service<TEntity> : IService<TEntity> where TEntity : Entity
{
    private readonly IUnitOfWork _unitOfWork;
    public Service(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
    public async Task<TEntity> AddAsync(TEntity entity)
    {
        //await _unitOfWork.TEntity.AddAsync(entity);
        await _unitOfWork.Products.AddAsync(entity);
        await _unitOfWork.CommitAsync();
        return entity;
    }
}

public class ProductService : Service<Product>,IProductService
{
    public ProductService(IUnitOfWork unitOfWork):base(unitOfWork)
    {

    }
}

public class UnitOfWork : IUnitOfWork
{
    private readonly ECommerceDbContext _context;
    private IProductRepository _productRepository;

    public UnitOfWork(ECommerceDbContext context)
    {
        _context = context;
    }

    public IProductRepository Products => _productRepository = _productRepository ?? new ProductRepository(_context);

    public async Task<int> CommitAsync()
    {
        return await _context.SaveChangesAsync();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

Братан, это не универсально, представьте, что это много сущностей, таких как Продукт. Другие организации также будут использовать этот метод. Таким образом, вы не можете добавить сюда _unitOfWork.Products. ProductRepository, унаследованный от Repository, прямо сейчас использует одни и те же методы.

alihangir 24.12.2020 01:36

Ой! Я пропустил это. Нужна общая единица работы. Попробуйте на stackoverflow.com/questions/39343750/generic-unit-of-work

Prasad Ramireddy 24.12.2020 02:31

Кажется, Generic UnitOfWork решит проблему, но я не могу реализовать Generic UnitOfWork. Любая помощь будет приветствоваться.

alihangir 24.12.2020 03:18
Ответ принят как подходящий

Поскольку каждый тип сущности является строго типизированным объектом, вы не можете обобщить свою реализацию без отражения. Распространенным обходным путем является предоставление универсального обратного вызова, в то же время позволяя инициатору предоставлять конкретное сопоставление для каждого типа объекта.

public class Service<TEntity> : IService<TEntity> where TEntity : class
{
    private readonly IUnitOfWork _unitOfWork;
    public Service(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
    
    public async Task<TEntity> AddAsync(TEntity entity, 
        Func<TEntity, IUnitOfWork, Task> addEntityCallback)
    {
        await addEntityCallback(entity, _unitOfWork);
        await _unitOfWork.CommitAsync();
        return entity;
    }
}

public interface IService<TEntity> where TEntity : class
{
    Task<TEntity> AddAsync(TEntity entity, Func<TEntity, IUnitOfWork, Task> addEntityCallback);
}

Затем вы можете вызвать IService.AddAsync с определенным сопоставлением:

public class ProductService : IProductService
{
    private readonly IService<Product> _service;
    public ProductService(IService<Product> service)
    {
        _service = service;
    }

    public async Task<Product> CreateProduct(Product newProduct)
    {
        await _service.AddAsync(newProduct, 
            (entity, unitOfWork) => unitOfWork.Products.AddAsync(entity));
        return newProduct;
    }
}

Обновление: в случае, если вы хотите всегда наследовать Service<TEntity> (согласно вашему комментарию), вы можете использовать абстрактный метод, который работает аналогично параметру обратного вызова. Это позволяет вам по-прежнему инкапсулировать логику в ProductService, но теперь вам больше не требуется предоставлять метод создания.

public class ProductService : Service<Product>, IProductService
{
    public ProductService(IUnitOfWork unitOfWork) : base(unitOfWork)
    {
    }

    protected override async Task<Product> AddEntityCallback(Product entity, 
        IUnitOfWork unitOfWork)
    {
        await unitOfWork.Products.AddAsync(entity);
        return entity;
    }
}

public abstract class Service<TEntity> : IService<TEntity> where TEntity : class
{
    private readonly IUnitOfWork _unitOfWork;
    public Service(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    protected abstract Task<TEntity> AddEntityCallback(TEntity entity, 
        IUnitOfWork unitOfWork);
    
    public async Task<TEntity> AddAsync(TEntity entity)
    {
        await AddEntityCallback(entity, _unitOfWork);
        await _unitOfWork.CommitAsync();
        return entity;
    }
}

Здесь есть небольшое недоразумение. Product Service должен наследоваться от базового класса Service, это моя вина, я забыл его указать. Таким образом, нам не нужно будет снова и снова определять методы вставки, обновления, удаления в каждом сервисе. Другими словами, в ProductService не должно быть CreateProduct, я планировал вызывать его из класса Service в Controller.

alihangir 24.12.2020 03:54

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

David L 24.12.2020 04:29

У меня есть действие ProductController и async Task<IActionResult> NewProduct(SaveProductDto saveProductDto) в контроллере. Мне просто нужно вызвать AddAsync в этом действии из ProductService, унаследовав его от базовой службы. Например, _productService.AddAsync(сделать что-нибудь)

alihangir 24.12.2020 04:31

Верно, и обновленный подход позволит это сделать.

David L 24.12.2020 04:35

Работает очень хорошо, спасибо. Я должен упомянуть, что ProductService также хотел реализовать это: async Task<Product> IProductService.AddEntityCallback(Product entity, IUnitOfWork unitOfWork). Я не знаю, почему это заставило меня сделать это. У нас есть как 1) защищенное асинхронное переопределение Task<Product> AddEntityCallback (сущность продукта, IUnitOfWork unitOfWork), так и 2) async Task<Product> IProductService.AddEntityCallback (сущность продукта, IUnitOfWork unitOfWork) одновременно.

alihangir 24.12.2020 06:02

Другие вопросы по теме