Ответ
Распространенный подход — использование шаблона 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-репозиторий предоставляет простой доступ к данным, а сервисный слой инкапсулирует как эти операции сочетаются для выполнения бизнес-требований, обеспечивая разделение ответственности.