← Назад к вопросам

Какие подходы используешь при работе с моделью данных?

2.0 Middle🔥 171 комментариев
#Entity Framework и ORM

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Подходы к работе с моделью данных в C# Backend

При проектировании и работе с моделью данных я придерживаюсь комбинированной стратегии, которая эволюционировала с опытом и изменениями в экосистеме .NET. Вот ключевые методологии, которые я применяю.

Основные принципы проектирования

  1. Domain-Driven Design (DDD)
    Я активно использую DDD для сложных бизнес-доменов. Это включает:
    • Выделение ограниченных контекстов (Bounded Contexts) с четкими границами
    • Создание богатых моделей (Rich Domain Models) с инкапсулированной бизнес-логикой
    • Использование агрегатов (Aggregates) для обеспечения целостности данных
    • Применение value objects для неделимых концепций (Email, Money, Address)
// Пример агрегата в DDD
public class Order : AggregateRoot
{
    private readonly List<OrderItem> _items = new();
    
    public OrderId Id { get; private set; }
    public CustomerId CustomerId { get; private set; }
    public OrderStatus Status { get; private set; }
    
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
    
    public void AddItem(ProductId productId, int quantity, decimal price)
    {
        // Бизнес-правила валидации
        if (Status != OrderStatus.Draft)
            throw new DomainException("Cannot modify finalized order");
            
        var item = OrderItem.Create(productId, quantity, price);
        _items.Add(item);
    }
}
  1. Принцип разделения ответственности
    Я четко разделяю:
    • Модели домена (Domain Models) — содержат бизнес-логику
    • Модели представления (View Models/DTOs) — для API ответов
    • Модели запросов (Request Models) — для входящих данных API
    • Модели БД (Entity Framework Entities) — для персистентности

Подходы к персистентности

  1. Entity Framework Core как ORM
    Использую EF Core с учетом следующих практик:
    • Code-First подход с миграциями
    • Конфигурация через Fluent API (а не атрибуты)
    • Оптимизация запросов через .AsNoTracking(), .Select(), .Include()
    • Шаблон Repository для абстракции доступа к данным
// Репозиторий с использованием EF Core
public class OrderRepository : IOrderRepository
{
    private readonly AppDbContext _context;
    
    public OrderRepository(AppDbContext context) => _context = context;
    
    public async Task<Order?> GetByIdAsync(OrderId id, CancellationToken ct)
    {
        return await _context.Orders
            .Include(o => o.Items)
            .FirstOrDefaultAsync(o => o.Id == id, ct);
    }
    
    public async Task AddAsync(Order order, CancellationToken ct)
    {
        await _context.Orders.AddAsync(order, ct);
    }
}
  1. Работа с контекстом БД
    • Использую один контекст на запрос (scoped lifetime)
    • Применяю Unit of Work паттерн через SaveChangesAsync()
    • Для сложных транзакций использую явные транзакции с изоляцией

Стратегии миграции и эволюции схемы

  1. Подход к изменениям модели

    • Все изменения через миграции EF Core
    • Обратно совместимые изменения при наличии работающего продакшена
    • Поэтапный деплой изменений схемы
  2. Обработка версионности данных

    • Использую оптимистичную блокировку через [Timestamp] или ConcurrencyToken
    • Для аудита применяю теневые свойства (Shadow Properties) в EF Core

Производительность и оптимизация

  1. Пагинация и фильтрация
    Всегда реализую пагинацию для коллекций:
public class PagedList<T> : List<T>
{
    public int PageNumber { get; }
    public int PageSize { get; }
    public int TotalCount { get; }
    public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
    
    public PagedList(IEnumerable<T> items, int count, int pageNumber, int pageSize)
    {
        PageNumber = pageNumber;
        PageSize = pageSize;
        TotalCount = count;
        AddRange(items);
    }
}

// Использование в репозитории
public async Task<PagedList<Order>> GetOrdersAsync(
    int pageNumber, 
    int pageSize, 
    CancellationToken ct)
{
    var query = _context.Orders.AsQueryable();
    var count = await query.CountAsync(ct);
    var items = await query
        .Skip((pageNumber - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync(ct);
        
    return new PagedList<Order>(items, count, pageNumber, pageSize);
}
  1. Кэширование стратегий
    • Redis для распределенного кэширования
    • MemoryCache для локального кэша
    • Паттерн Cache-Aside для сложных запросов

Валидация и безопасность

  1. Многоуровневая валидация

    • FluentValidation для валидации DTO
    • Аннотации данных для простых случаев
    • Валидация на уровне домена в методах агрегатов
  2. Защита данных

    • Автомаппинг с проверкой (AutoMapper с конфигурацией)
    • Принцип минимальных привилегий в запросах
    • Экранирование от SQL-инъекций через параметризованные запросы

Современные тенденции

  1. CQRS и MediatR
    Для сложных систем применяю CQRS:

    • Разделение команд (изменения) и запросов (чтения)
    • Использование MediatR для обработки команд
    • Оптимизированные модели чтения
  2. Event Sourcing
    В системах с требованием полного аудита и временных путешествий:

    • Хранение событий вместо состояния
    • Проекции для представлений данных

Заключение

Мой подход к работе с моделью данных — это прагматичный баланс между чистотой архитектуры и практическими требованиями проекта. Я начинаю с простой модели и усложняю её только при реальной необходимости, следуя принципу YAGNI (You Aren't Gonna Need It). Ключевое — обеспечить поддерживаемость, производительность и эволюционность системы с течением времени.

Каждый из перечисленных подходов применяется в зависимости от: масштаба проекта, требований к производительности, команды разработки и бизнес-требований. Гибкость в выборе правильного инструмента для задачи — важнейший навык senior-разработчика.