Управление Persistence данных
Persistence данных в Lino генерируется вокруг границ Clean Architecture: доменная модель остается независимой от Entity Framework Core, а вопросы базы данных находятся в Infrastructure.Persistence. Это сохраняет фокус сущностей на бизнес-правилах и размещает mappings, provider configuration, migrations, repositories, transactions и initialization в infrastructure layer.
При создании сервиса с lino service new Lino фиксирует важные решения по persistence, такие как архитектура сервиса и data provider. Текущие варианты provider: SqlServer и PostgreSql. В традиционных сервисах persistence генерируется непосредственно под сервисом. В модульных сервисах каждый модуль получает собственный проект Infrastructure.Persistence и собственный ApplicationDbContext, а база данных остается общей для сервиса.
В традиционных или модульных сервисах база данных одна на сервис. В модульных сервисах каждый модуль сопоставляется с отдельной schema внутри той же базы данных. Это позволяет каждому bounded context сохранять отдельное логическое namespace, с функциональной изоляцией, более понятным versioning и более организованными migrations.
Эта страница объясняет, как Lino организует конфигурации Entity Framework Core, регистрацию DbContext, repositories, IUnitOfWork, transactions, поток Transactional Outbox и цикл migrations, предоставляемый CLI.
Важно: сгенерированный persistence code является production-oriented scaffolding. Всегда проверяйте mappings, constraints, indexes, delete behavior, transactions и migration scripts перед применением изменений к shared или production базам данных.
Конфигурации типов сущностей
Lino следует принципу Persistence-Ignorant: доменные сущности не знают деталей инфраструктуры. Весь ORM mapping находится в классах, которые реализуют IEntityTypeConfiguration<TEntity> и расположены в Infrastructure.Persistence/Configurations. Поэтому сущности выражают бизнес-поведение, а инфраструктура определяет, как они будут храниться.
Файлы конфигурации генерируются на основе решений, принятых в CLI: primary keys, strongly typed IDs, обязательные поля, размеры строк, Value Objects, связи, перечисления, поля аудита, tenant fields и дочерние сущности. Сгенерированный результат следует рассматривать как надежную отправную точку, а не как код, который никогда не требует проверки.
Что обычно определяют конфигурации
- Таблица и schema: имя таблицы и schema текущего сервиса или модуля.
- Primary keys: идентификаторы сущностей, преобразования strongly typed IDs и поведение генерации ключей.
- Свойства: обязательные поля, максимальная длина, типы колонок, сохранение enum, owned structures для Value Objects и колонки metadata файлов.
- Связи: one-to-one, one-to-many, many-to-many, child collections, foreign keys и поведение удаления.
- Индексы и constraints: уникальность, производительность lookup, tenant-aware уникальность и согласованность на уровне базы данных.
Глобальные соглашения
Повторяющиеся соглашения можно централизовать в ModelConfiguration или эквивалентных helpers: decimal precision, collation, преобразование DateTime, naming convention, глобальные фильтры, аудируемые свойства и общие правила для multi-tenant полей.
Как применяются конфигурации
Сгенерированный ApplicationDbContext применяет конфигурации в OnModelCreating. По умолчанию Lino использует Source Generators, чтобы регистрировать конфигурации производительнее и без Reflection. Когда используется scanning по assembly, конфигурация может выглядеть так:
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
Когда включен messaging, Lino также применяет конфигурацию OutboxMessage, чтобы integration events сохранялись в той же транзакции базы данных до того, как worker опубликует их асинхронно.
Checklist проверки
- Убедитесь, что имена таблиц, schemas и соглашения соответствуют границе модуля.
- Проверьте размеры строк, decimal precision, nullable колонки и сохранение enums перед генерацией migrations.
- Внимательно проверьте связи и delete behavior, особенно для aggregate roots и дочерних сущностей.
- Добавьте или скорректируйте индексы для queries, которые используются grids, filters, integrations и background jobs.
- Запустите build перед созданием migration, чтобы убедиться, что EF Core видит согласованную модель.
DbContexts
Сгенерированный ApplicationDbContext является контекстом EF Core для границы persistence сервиса или модуля. Он предоставляет свойства DbSet<TEntity> для сопоставленных сущностей и реализует IApplicationDbContext, позволяя query handlers и application services зависеть от application abstraction, а не от конкретного infrastructure класса.
Традиционные и модульные сервисы
- Традиционный сервис: persistence генерируется в
src/Services/<ServiceName>/Infrastructure.Persistence, а API сервиса используется как startup project для migrations. - Модульный сервис: каждый модуль имеет собственный проект
src/Services/<ServiceName>/Modules/<ModuleName>/Infrastructure.Persistenceи собственныйApplicationDbContext. Host сервиса используется как startup project для migrations.
В модульных сервисах такое разделение делает каждый bounded context явным в коде. Модули могут развивать свою модель persistence независимо, даже если они работают в одной базе данных сервиса, когда выбрана такая архитектура.
Регистрация и настройка provider
Lino генерирует регистрацию persistence через extension для IHostApplicationBuilder. Сгенерированный код регистрирует Unit of Work, repositories, domain services, pooled factory для ApplicationDbContext, scoped ApplicationDbContext и abstraction IApplicationDbContext.
Provider-specific конфигурация генерируется в соответствии с базой данных, выбранной для сервиса:
UseSqlServer(...)для сервисов SQL Server.UseNpgsql(...).UseSnakeCaseNamingConvention()для сервисов PostgreSQL.MigrationsHistoryTable(Constants.Database.MigrationsHistoryTable, Constants.Database.Schema)для хранения истории migrations в ожидаемых таблице и schema.- Provider-specific constraint validators регистрируются, чтобы преобразовывать нарушения базы данных в согласованные application errors.
Tenant-aware contexts
Когда модуль содержит tenant-aware сущности, сгенерированный context может включать состояние tenant и глобальные query filters. В этом сценарии scoped factory создает context, настроенный для текущего scope, чтобы handlers не повторяли tenant filters вручную во всех местах чтения и записи.
Repositories
Lino генерирует repository interfaces в domain layer и concrete implementations в Infrastructure.Persistence/Repositories. Это сохраняет направление зависимостей: domain определяет нужный ему persistence contract, а infrastructure предоставляет реализацию с Entity Framework Core.
Concrete repositories также могут находиться в <ModuleName>/Infrastructure.Persistence.Repositories и реализовывать interfaces из <ModuleName>/Domain.Repositories, в зависимости от структуры сервиса. Важный момент в том, что application и domain не зависят напрямую от concrete деталей EF Core.
Repositories в основном используются command handlers и domain-oriented flows, которым нужно сохранять aggregates. Query Handlers могут использовать IApplicationDbContext напрямую, когда им нужны оптимизированные read projections, filters, pagination и результаты в DTO format.
Обязанности repository
- Загружать aggregate roots и дочерние данные, необходимые command handlers.
- Добавлять, обновлять и удалять aggregates в соответствии с persistence model.
- Инкапсулировать persistence queries, которые являются частью поведения aggregate или write orchestration.
- Держать EF Core-specific операции вне domain project.
- Инкапсулировать сложные queries, когда они относятся к write flow, включая LINQ,
FromSqlи вспомогательные projections.
Практические рекомендации
- Используйте repositories для write use cases, которым нужно сохранять consistency aggregate.
- Для чтения используйте query projections вместо загрузки полных aggregates только ради сборки DTO.
- Держите методы repository явными; избегайте generic methods, которые открывают application произвольные persistence операции.
- Проверяйте includes и tracking, когда handler изменяет aggregate, child collection или many-to-many relationship.
- Открывайте только методы, необходимые domain и application, сохраняя repository aggregate-root-centric.
Unit of Work
IUnitOfWork является транзакционной границей, сгенерированной Lino. Он координирует persistence с EF Core, transaction control, публикацию domain events и сохранение в Outbox для integration events. Handlers используют эту abstraction, чтобы явно подтверждать изменения, вместо прямого вызова DbContext.SaveChangesAsync во всех местах application.
В простых сценариях сам DbContext часто достаточен как unit of work. Тем не менее Lino генерирует выделенную реализацию UnitOfWork, чтобы обеспечить согласованный контроль над transactions, events и интеграцией с Outbox.
Сгенерированные операции
SaveChangesAsync(cancellationToken): сохраняет изменения и публикует domain events, когда это настроено.SaveChangesAsync(publishDomainEvents, cancellationToken): позволяет сохранять с публикацией domain events или без нее.SaveChangesInTransactionAsync(cancellationToken): открывает transaction, сохраняет изменения и выполняет commit.BeginTransactionAsync,CommitAsyncиRollbackAsync: позволяют явно управлять transaction.CommitOrRollbackAsync: выполняет commit или rollback transaction на основеResult.
Domain events и Outbox
Когда существуют domain events, Lino требует открытую transaction перед их публикацией. Это сделано намеренно. Если domain event создает integration events, эти events регистрируются в Outbox и сохраняются в той же transaction. Затем worker может обработать записи Outbox и опубликовать messages через настроенную messaging infrastructure.
Этот flow защищает consistency: когда command создает entities, которые поднимают events, и проект использует Outbox, handler должен сохранять через transaction, например с SaveChangesInTransactionAsync, или явно открыть и подтвердить transaction. Без этой transaction infrastructure должна завершиться ошибкой, а не разрешить ненадежный event flow.
Когда использовать каждый стиль save
- Используйте
SaveChangesAsyncдля прямой persistence без требования consistency между events и Outbox. - Используйте
SaveChangesInTransactionAsync, когда domain events и persistence в Outbox должны быть частью одного commit. - Используйте
BeginTransactionAsync,CommitAsyncиRollbackAsync, когда handler имеет несколько шагов и должен принять итоговое решение на основеResult.
Практическое правило: если write operation публикует domain events, которые могут произвести integration events, держите database changes и Outbox records в одной transaction.
Managing Migrations
Lino оборачивает цикл migrations Entity Framework Core CLI-командами, которые знают service, module, architecture, provider, DbContext, startup project, version file и output location для scripts. Это снижает количество ошибок ручных команд и сохраняет migration metadata отслеживаемой моделью проекта Lino.
Migrations фиксируют эволюцию базы данных на основе изменений в entities, Value Objects, relationships, indexes и конфигурациях Entity Framework. Перед созданием migration скомпилируйте solution или хотя бы затронутый service, чтобы EF Core загрузил текущую модель.
Создать migration
lino database migrations add --service <ServiceName> --module <ModuleName>
Команда запрашивает service, module, текущую версию service в src/Services/<ServiceName>/version.txt и описание migration. Lino создает имя с использованием version и sequence, затем выполняет dotnet ef migrations add для корректных persistence project и startup project.
Lino также генерирует SQL script для migration с помощью dotnet ef migrations script. Script записывается под затронутым persistence project в versioned folder по шаблону:
Infrastructure.Persistence/Scripts/<Version>/<Sequence>_<Description>.sql Infrastructure.Persistence/Scripts/v1.2.3/001_AddCustomerIsActive.sql
Примечание: помимо файлов .cs, сгенерированных Entity Framework, Lino генерирует соответствующий .sql script с DDL-инструкциями. Это упрощает review для infrastructure teams, DBAs и controlled deployment процессов.
Применить, вывести список, откатить и удалить
lino database migrations add --service <ServiceName> --module <ModuleName> lino database migrations list --service <ServiceName> --module <ModuleName> lino database migrations apply --service <ServiceName> --module <ModuleName> lino database migrations revert --service <ServiceName> --module <ModuleName> lino database migrations remove --service <ServiceName> --module <ModuleName>
add: создает migration для ожидающих изменений в модели.list: выводит известные migrations для выбранного context.apply: выполняетdotnet ef database update, чтобы применить migrations к настроенной базе данных.revert: возвращает последний успешный migration path, когда workflow это допускает.remove: удаляет последнюю запись созданной migration и сгенерированные EF files, когда применимо.
Каноническая группа команд: database migrations. Aliases, такие как db и migration, могут существовать для productivity, но документация должна предпочитать полную форму.
Что Lino выбирает за вас
- В традиционных сервисах EF project:
src/Services/<ServiceName>/Infrastructure.Persistence, а startup project:src/Services/<ServiceName>/Api. - В модульных сервисах EF project:
src/Services/<ServiceName>/Modules/<ModuleName>/Infrastructure.Persistence, а startup project:src/Services/<ServiceName>/Host. - Context является сгенерированным
ApplicationDbContextдля выбранного service или module. - Таблица history migrations EF настраивается с
Constants.Database.MigrationsHistoryTableиConstants.Database.Schema.
Рекомендуемый workflow
- Смоделируйте или измените entities, Value Objects, relationships, indexes или persistence configurations.
- Запустите build и исправьте ошибки компиляции перед генерацией migration.
- Выполните
lino database migrations addдля корректных service и module. - Проверьте сгенерированные C# migration и SQL script перед применением.
- Примените migration в local или development environment и проверьте database schema.
- Закоммитьте migration files и SQL scripts вместе с изменением domain или application, которое потребовало изменение.
Checklist: проверьте migration diff, примените к local database, запустите build и протестируйте затронутый flow перед публикацией.
