Managing Data Persistence

In this section, you will learn how Lino configures Entity Framework Core so that the domain layer remains fully isolated and the database migration processes are predictable and controlled — whether in a traditional (monolithic) service or a modular service.


When creating a service via the lino new service command, the CLI asks for two essential decisions:

  • Architecture — traditional or modular service.
  • Data provider — SqlServer or PostgreSql (other providers will be added in future versions).

In traditional (monolithic) or modular (modular monolith) services, the database is unique per service. However, in modular services, each module is mapped to a distinct schema within the same database. This allows each bounded context to maintain a separate logical namespace, ensuring functional isolation, independent versioning, and more organized and safer migrations.

Entity Type Configurations

Lino follows the Persistence‑Ignorant principle: domain entities do not know infrastructure details. To achieve this, all ORM mapping is moved to classes that implement IEntityTypeConfiguration<T>, located in Configurations/<EntityName>Configuration.cs.

  • Each entity has a dedicated configuration file.
  • Global conventions (e.g., decimal(18,2), collation, DateTime as utc) can be centralized in ModelConfiguration.
  • Configurations are applied in the OnModelCreating method of the DbContext via modelBuilder.ApplyConfigurationsFromAssembly(...).

DbContexts

The DbContext represents the application's unit of transaction. Lino automatically generates:

  • Traditional service — a single AppDbContext containing all DbSet<TEntity>.
  • Modular service — a <Module>DbContext for each bounded context. This way, each module compiles faster, has shorter migration cycles, and reduces the risk of merge conflicts.

All DbContext instances are registered in <Module>/Infrastructure.Persistence, exposed via IUnitOfWork, and resolved through dependency injection.

Repositories

Concrete repositories reside in <Module>/Infrastructure.Persistence.Repositories and implement the interfaces defined in the <Module>/Domain.Repositories namespace. Each repository:

  • Encapsulates complex queries (LINQ, FromSql, DTO projections).
  • Exposes only the methods necessary for the domain (Aggregate Root-centric).

Unit of Work

The Unit of Work acts as a transactional boundary that coordinates multiple repositories, ensuring that all operations are executed atomically. In simple scenarios, the DbContext itself is often sufficient. However, Lino generates a dedicated implementation (UnitOfWork) that offers greater control and flexibility.

  • Allows manual control of transactions using BeginTransaction, Commit, and Rollback, when needed.
  • Publishes domain events during the current transaction, ensuring they occur within the same commit cycle.
  • Persists integration events to the outbox, following the Transaction Outbox Pattern, ensuring reliable delivery in distributed architecture scenarios (when applicable).

Managing Migrations

Lino fully simplifies and automates the Database Migrations process with Entity Framework Core, eliminating repetitive tasks and common risks of inconsistency. To create a new migration, run the following command:

lino database migrations add

When running the command, an interactive wizard will prompt you for the following information:

  • Service — The name of the project where the migration will be applied.
  • Module — (only for modular services) the module to which the change belongs.
  • Migration Name — Enter a description of the change (e.g., AddCustomerIsActive). Lino will automatically handle the current version and incremental ordering when generating the .sql script, following the pattern: /Scripts/v1.2.3/001_AddCustomerIsActive.sql.
Note: Besides the .cs files generated by Entity Framework, Lino also automatically creates the corresponding .sql script containing the DDL instructions. This facilitates manual execution of changes or review by infrastructure teams and DBAs.
An unhandled error has occurred. Reload đź—™