| name | drn-entityframework |
| description | DRN.Framework.EntityFramework - DrnContext, migrations, entity lifecycle tracking, Npgsql configuration, repositories, and named repository cancellation scopes. Keywords: drncontext, ef-core, migrations, database, postgresql, npgsql, repository-implementation, repository-cancellation, cancellation-scope, entity-tracking, dbcontext-configuration, prototype-mode, testcontainers |
| last-updated | "2026-07-15T00:00:00.000Z" |
| difficulty | advanced |
| tokens | ~2.5K |
DRN.Framework.EntityFramework
Convention-based EF Core integration with automatic configuration and migrations.
When to Apply
- Creating new DbContexts
- Setting up database migrations
- Configuring development database connections
- Understanding Source-Known entity lifecycle tracking
- Working with Testcontainers for local development
DrnContext Pattern
public class QAContext : DrnContext<QAContext>
{
public QAContext(DbContextOptions<QAContext> options) : base(options) { }
public QAContext() : base(null) { }
public DbSet<User> Users { get; set; }
public DbSet<Question> Questions { get; set; }
}
| Feature | Description |
|---|
| Auto-registration | Via AddServicesWithAttributes() |
| Convention naming | Connection string: ConnectionStrings:QAContext |
| Config discovery | Auto-applies IEntityTypeConfiguration from the context assembly when the configuration namespace matches the context namespace or one of its child namespaces |
| Entity tracking | Auto-marks entities as Created/Modified/Deleted with timestamps |
| Design-time | Implements IDesignTimeDbContextFactory |
Configurations are auto-discovered from the context assembly when the configuration namespace matches the context namespace or one of its child namespaces. Child namespaces such as Sample.Infra.QA.Configurations are valid for a Sample.Infra.QA context.
Augmented Entity Behavior
DrnContext augments entities during OnModelCreating and runtime:
| Feature | Mechanism |
|---|
| ID Generation | IDrnSaveChangesInterceptor assigns collision-free long IDs for new entities |
| Property Init | IDrnMaterializationInterceptor initializes EntityIdSource and injects ISourceKnownEntityIdOperations (EntityIdOps) |
| Secure ↔ Plain | ToSecure / ToPlain on entity and repository for idempotent ID form conversion |
| JSON Models | IEntityWithModel<T> auto-maps .Model to jsonb column |
| Identity Naming | ASP.NET Core Identity tables/columns → snake_case for PostgreSQL |
| Startup Validation | Validates all entities have valid, unique [EntityType] attributes |
Connection String Conventions
Production/Staging
Explicit connection strings required via appSettings.GetRequiredConnectionString(contextName):
{ "ConnectionStrings": { "QAContext": "Host=prod-db;Port=5432;Database=qa;..." } }
Local Dev with Testcontainers (LaunchExternalDependencies)
When DrnDevelopmentSettings:LaunchExternalDependencies = true, the framework auto-starts PostgreSQL via Testcontainers.
Setup (Debug-only reference to DRN.Framework.Testing):
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<ProjectReference Include="..\DRN.Framework.Testing\DRN.Framework.Testing.csproj" />
</ItemGroup>
#if DEBUG
public class SampleProgramActions : DrnProgramActions
{
public override async Task ApplicationBuilderCreatedAsync<TProgram>(
TProgram program, WebApplicationBuilder builder,
IAppSettings appSettings, IScopedLog scopedLog)
{
var launchOptions = new ExternalDependencyLaunchOptions
{
PostgresContainerSettings = new PostgresContainerSettings
{
Reuse = true,
HostPort = 6432
}
};
await builder.LaunchExternalDependenciesAsync(scopedLog, appSettings, launchOptions);
}
}
#endif
Containerized Development (Docker Compose)
Set postgres-password env var to trigger auto-connection string generation:
Environment=Development, postgres-password=dev-password, DrnContext_DevHost=postgres
| Key | Default | Purpose |
|---|
DrnContext_DevHost | drn | Database host |
DrnContext_DevPort | 5432 | Database port |
DrnContext_DevUsername | drn | Database user |
DrnContext_DevDatabase | drn | Database name |
postgres-password | (required) | Triggers auto-connection string |
[!CAUTION]
postgres-password and DrnContext_Dev* keys are ignored in non-Development environments.
Migrations
dotnet ef migrations add MigrationName --context QAContext --project Sample.Infra
dotnet ef database update --context QAContext
[!TIP]
Use the project containing DrnContext as startup project — it implements IDesignTimeDbContextFactory.
Prototype Mode
Auto-recreates the local development database on pending model changes. All conditions must be true:
NpgsqlDbContextOptionsAttribute.UsePrototypeMode = true
DrnDevelopmentSettings:Prototype = true
- Application environment is Development
DrnDevelopmentSettings:AutoMigrateDevelopment = true
- Pending model changes exist
- No migrations have been applied, or applied migrations exist and
UsePrototypeModeWhenMigrationExists = true
[!WARNING]
Prototype mode drops and recreates the database. It is Development-only; AutoMigrateStaging applies migrations only and must not enable prototype recreation.
Canonical invariants: Maintenance Reference: Migration And Prototype Invariants.
Entity Configuration
Configurations are auto-discovered from the context's assembly namespace:
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasKey(u => u.Id);
builder.Property(u => u.Username).HasMaxLength(100);
}
}
[!CAUTION]
Navigation Configuration Ordering: Navigation-level configurations (e.g., AutoInclude()) must be placed after relationship definitions in Fluent API. Placing them before silently fails. See dotnet/efcore#31380.
builder.Navigation(x => x.Books).AutoInclude();
builder.HasMany(x => x.Books).WithMany();
builder.HasMany(x => x.Books).WithMany();
builder.Navigation(x => x.Books).AutoInclude();
DB Context Attributes
| Attribute | Purpose |
|---|
[NpgsqlDbContextOptions] | Postgres options, UsePrototypeMode, SeedAsync |
[DrnContextDefaults] | Standard DRN conventions (naming, discovery) |
[DrnContextPerformanceDefaults] | Performance optimizations (see below) |
NpgsqlDbContextOptions Configuration Hooks
public virtual void ConfigureNpgsqlOptions<TContext>(NpgsqlDbContextOptionsBuilder builder, IServiceProvider? serviceProvider);
public virtual void ConfigureNpgsqlDataSource<TContext>(NpgsqlDataSourceBuilder builder, IServiceProvider serviceProvider);
public virtual void ConfigureDbContextOptions<TContext>(DbContextOptionsBuilder builder, IServiceProvider? serviceProvider);
public virtual Task SeedAsync(IServiceProvider serviceProvider, IAppSettings appSettings);
Performance Defaults
| Setting | Default | Purpose |
|---|
MaxAutoPrepare | 200 | Prepared statement cache size |
AutoPrepareMinUsages | 5 | Minimum usages before auto-prepare |
MinPoolSize/MaxPoolSize | 1/15 | Connection pool bounds |
Read/WriteBufferSize | 8192 | I/O buffers |
CommandTimeout | 30 | Command timeout in seconds |
Custom performance attributes can inherit NpgsqlPerformanceSettingsAttribute to override defaults.
RepositorySettings
Configure repository-wide query behavior:
public class RepositorySettings<TEntity>
{
public bool AsNoTracking { get; set; }
public bool IgnoreAutoIncludes { get; set; }
public IReadOnlyDictionary<string, Expression<Func<TEntity, bool>>> Filters { get; }
public void AddFilter(string name, Expression<Func<TEntity, bool>> filter);
public bool RemoveFilter(string name);
public void ClearFilters();
}
Override EntitiesWithAppliedSettings in custom repositories for global includes/filters. See drn-domain-design for examples.
Repository Cancellation
Repository operations resolve a child cancellation scope instead of using the root:
CancellationToken is read-only, CancelWhen(token) links a lifetime token, and CancelChanges cancels the repository group.
RepositoryCancellationScopeKey defaults to the concrete repository type, so instances of the same type share cancellation within the current DI scope.
- Override the key only to select another shared group; never create keys from request or operation data.
- Use
cancellation.Root.Cancel() for cancel-all behavior and a local linked source for one operation.
using var operationSource =
CancellationTokenSource.CreateLinkedTokenSource(
repository.CancellationToken,
operationToken);
Shared Defaults Reference
Canonical DrnDevelopmentSettings, connection modes, prototype invariants, and container defaults live in the DRN Framework Maintenance Reference.
Testing Integration
See drn-testing for full ContainerContext details.
[Theory]
[DataInline]
public async Task Test(DrnTestContext context)
{
context.ServiceCollection.AddInfraServices();
await context.ContainerContext.Postgres.ApplyMigrationsAsync();
var qaContext = context.GetRequiredService<QAContext>();
}
Related Skills
Global Usings
global using DRN.Framework.EntityFramework.Context;
global using Microsoft.EntityFrameworkCore;
global using DRN.Framework.Utils.DependencyInjection;