Приведи пример совмещения сгенерированных CRUD-операций и кастомной бизнес-логики в приложении

Ответ

Распространенный подход — использование шаблона Repository для базового CRUD и выделение Service Layer для сложной бизнес-логики. CRUD-операции могут быть сгенерированы инструментами (например, шаблонами Scaffold в ASP.NET Core).

1. Уровень данных (Repository / Generic Repository) - Сгенерированный/стандартный CRUD:

// Обобщенный репозиторий (может быть сгенерирован)
public interface IRepository<T> where T : class
{
    Task<T?> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task AddAsync(T entity);
    void Update(T entity); // Обычно не async, т.к. только меняет состояние в контексте
    Task DeleteAsync(int id);
    // ... другие базовые методы
}

public class EfRepository<T> : IRepository<T> where T : class
{
    private readonly AppDbContext _context;
    private readonly DbSet<T> _dbSet;

    public EfRepository(AppDbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }

    public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
    public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();
    public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
    public void Update(T entity) => _dbSet.Update(entity);
    public async Task DeleteAsync(int id)
    {
        var entity = await GetByIdAsync(id);
        if (entity != null) _dbSet.Remove(entity);
    }
    // SaveChanges обычно вызывается на уровне Unit of Work или сервиса
}

2. Уровень бизнес-логики (Service) - Кастомная логика:

// Сервис, который использует репозиторий и добавляет бизнес-правила
public class ProductService
{
    private readonly IRepository<Product> _productRepository;
    private readonly IEmailService _emailService; // Пример другой зависимости

    public ProductService(IRepository<Product> productRepository, IEmailService emailService)
    {
        _productRepository = productRepository;
        _emailService = emailService;
    }

    // Кастомный метод: получение товаров со скидкой
    public async Task<IEnumerable<Product>> GetDiscountedProductsAsync()
    {
        var allProducts = await _productRepository.GetAllAsync();
        // Бизнес-правило: скидка применяется к товарам старше 30 дней
        return allProducts.Where(p => (DateTime.Now - p.CreatedDate).Days > 30)
                          .Select(p => new Product { 
                              Id = p.Id, 
                              Name = p.Name, 
                              Price = p.Price * 0.8m // 20% скидка
                          });
    }

    // Кастомный метод: размещение заказа с проверками и побочными эффектами
    public async Task PlaceOrderAsync(int productId, int quantity, string userId)
    {
        // 1. Использование CRUD: получить товар
        var product = await _productRepository.GetByIdAsync(productId);
        if (product == null) throw new ArgumentException("Товар не найден.");
        if (product.Stock < quantity) throw new InvalidOperationException("Недостаточно товара на складе.");

        // 2. Бизнес-логика: обновить остаток
        product.Stock -= quantity;
        _productRepository.Update(product); // Использование CRUD: обновление

        // 3. Создание сложной сущности (Order) - это тоже часть логики
        var order = new Order { ProductId = productId, UserId = userId, Quantity = quantity };
        // ... сохранение заказа через свой репозиторий IRepository<Order>

        // 4. Побочный эффект: уведомление
        if (product.Stock < 10) // Бизнес-правило: низкий запас
        {
            await _emailService.SendLowStockAlertAsync(product.Name, product.Stock);
        }
    }
}

Итог: CRUD-репозиторий предоставляет простой доступ к данным, а сервисный слой инкапсулирует как эти операции сочетаются для выполнения бизнес-требований, обеспечивая разделение ответственности.

Ответ 18+ 🔞

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

Снизу, на уровне данных, у тебя сидит этот самый Repository. Его задача — тупо долбиться в базу, как дятел. Получи запись, вставь запись, обнови, удали. Никаких мозгов, чистейший CRUD. Его хоть автоматом сгенерировать можно, всем похуй.

// Вот этот чувак — тупой как пробка, но полезный.
public interface IRepository<T> where T : class
{
    Task<T?> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task AddAsync(T entity);
    void Update(T entity);
    Task DeleteAsync(int id);
}

А теперь поднимаемся на этаж выше, в Service. Вот тут уже начинается настоящая жизнь, ёпта! Тут тебе и проверки, и бизнес-правила, и рассылки уведомлений — вся эта ебальная логика, из-за которой проект вообще пишется.

public class ProductService
{
    private readonly IRepository<Product> _productRepository;
    private readonly IEmailService _emailService;

    public ProductService(IRepository<Product> productRepository, IEmailService emailService)
    {
        _productRepository = productRepository;
        _emailService = emailService;
    }

    // Вот смотри, пример: надо товары со скидкой получить.
    // Репозиторий бы тупо всё выгрузил, а сервис думает: "Ага, скидка только на старые товары!"
    public async Task<IEnumerable<Product>> GetDiscountedProductsAsync()
    {
        var allProducts = await _productRepository.GetAllAsync();
        return allProducts.Where(p => (DateTime.Now - p.CreatedDate).Days > 30)
                          .Select(p => new Product {
                              Id = p.Id,
                              Name = p.Name,
                              Price = p.Price * 0.8m // Бля, 20% отъём!
                          });
    }

    // А вот это уже серьёзнее: оформить заказ.
    // Тут одной вставкой в базу не отделаешься, тут целый цирк.
    public async Task PlaceOrderAsync(int productId, int quantity, string userId)
    {
        // 1. Достаём товар через тот самый тупой CRUD
        var product = await _productRepository.GetByIdAsync(productId);
        if (product == null) throw new ArgumentException("Товар-то не найден, гений!");
        if (product.Stock < quantity) throw new InvalidOperationException("На складе пусто, как в твоей голове!");

        // 2. Логика: списываем остатки
        product.Stock -= quantity;
        _productRepository.Update(product); // И обновляем через тот же простой метод

        // 3. Создаём заказ — это уже новая сущность, своя история
        var order = new Order { ProductId = productId, UserId = userId, Quantity = quantity };
        // ... тут бы его тоже через репозиторий заказа сохранить

        // 4. А вот и побочный эффект: если запасы на исходе — орем менеджерам
        if (product.Stock < 10)
        {
            await _emailService.SendLowStockAlertAsync(product.Name, product.Stock);
        }
    }
}

Короче, суть в чём: Repository — это тупой грузчик, который таскает ящики (данные) туда-сюда. А Service — это уже прораб, который решает, какие ящики, куда, в каком порядке таскать, и кому потом позвонить, чтобы объяснить, почему всё так долго. Разделение, блядь, обязанностей, чтобы не получилась одна большая помойка, где всё перемешано.