Skip to main content

Lagoon EF Core contexts

Lagoon provides EF Core base classes that integrate ASP.NET Identity, the framework's internal entities, and application conventions. The application context is usually named ApplicationDbContext and lives in the Model project.

Choosing the base class​

The choice depends first on the Identity key type, then on whether change tracking is needed.

Base classUser/role key typeChange trackingUser constraint
LgApplicationDbContext<TUser>stringNoTUser inherits from LgIdentityUser
LgApplicationDbContextGuid<TUser>GuidNoTUser inherits from LgIdentityUserGuid
LgApplicationTrackedChangeDbContext<TUser>stringYesTUser inherits from LgIdentityUser
LgApplicationTrackedChangeDbContextGuid<TUser>GuidYesTUser inherits from LgIdentityUserGuid

LgApplicationDbContextBase<TUser, TRole, TKey> inherits from IdentityDbContext<TUser, TRole, TKey>, implements ILgApplicationDbContext, and exposes common members such as Database, Eulas, SaveChangesAsync(...), Set<TEntity>(), and LoadAnonymizerRules().

tip
Guid keys

Recent Lagoon applications often use LgApplicationDbContextGuid<TUser> to work directly with Guid identifiers on the Identity side.

Structure of the application context​

A Lagoon context configures the EF Core provider in a static Configure(...) method, calls the inherited configuration in OnModelCreating(...), then adds project-specific rules.

ApplicationDbContext.cs
public class ApplicationDbContext : LgApplicationDbContextGuid<ApplicationUser>
{
internal protected const string CONNECTION_STRING_KEY = "SqliteDb";

public ApplicationDbContext(DbContextOptions options)
: base(options)
{ }

public ApplicationDbContext()
{ }

public static void Configure(DbContextOptionsBuilder builder, IConfiguration configuration, bool enableDetailedLogging = false)
{
// Configure le fournisseur EF Core et l'assembly qui contient les migrations.
builder.UseSqlite(
configuration.GetConnectionString(CONNECTION_STRING_KEY),
options => options.MigrationsAssembly(GetAssemblyName<ApplicationDbContext>()));

// Enregistre les jeux d'entités nécessaires à OpenIddict avec des clés Guid.
builder.UseOpenIddict<Guid>();

if (enableDetailedLogging)
{
// Active les détails utiles pendant les migrations et le diagnostic local.
builder.EnableDetailedErrors();
builder.EnableSensitiveDataLogging();
}
}

protected override void OnModelCreating(ModelBuilder builder)
{
// La configuration Lagoon/Identity doit être appliquée avant les règles projet.
base.OnModelCreating(builder);
SetUserNameRequired(builder);
SetUserEmailRequired(builder);
DisableCascadingDeletionBehavior(builder);
HasIdentityRoles<Roles>(builder);
}

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
base.ConfigureConventions(configurationBuilder);

// Enregistre les dates en UTC et les recharge en heure locale.
configurationBuilder.DateSaveAsUtcLoadAsLocal();
}
}
caution
OpenIddict key: UseOpenIddict<Guid>() is mandatory

Since Lagoon 4.0, OpenIddict entities always use a Guid primary key, regardless of the key type of ApplicationUser: an ApplicationUser : LgIdentityUser (string key) must also declare builder.UseOpenIddict<Guid>().

The server hard-codes this key type; a builder.UseOpenIddict() call without an argument (string key) makes the application startup fail with InvalidOperationException: Cannot create a DbSet for 'OpenIddictEntityFrameworkCoreApplication'.

The lgn update upgrade automatically rewrites the call and generates the corresponding EF migration. Review this migration before applying it: the string → Guid conversion empties the OpenIddict tables (tokens and authorizations are regenerated on sign-in, applications and scopes are reseeded at startup) and, on SQL Server, drops and recreates the primary keys, foreign keys, and indexes carried by the converted columns.

Useful Lagoon methods in OnModelCreating​

MethodRole
SetUserNameRequired(builder)Makes UserName and NormalizedUserName required on the Identity user.
SetUserEmailRequired(builder)Makes Email and NormalizedEmail required on the Identity user.
DisableCascadingDeletionBehavior(builder)Forces EF Core relationships to DeleteBehavior.NoAction.
HasIdentityRoles<TAppRole>(builder)Adds Identity roles from an enumeration.
UseNotifications<TNotification>(builder, ...)Declares the notification and user-association tables.

base.OnModelCreating(builder) must remain the first call: it configures Identity, LgSettings, Eulas, the optional collation, and Lagoon's internal entities.

The ILgApplicationDbContext interface​

ILgApplicationDbContext is the common contract used by dependency injection. It notably exposes:

MemberUsage
DatabaseAccess to core EF Core operations.
EulasTable of terms of use managed by Lagoon.
SaveChanges() / SaveChangesAsync(...)EF Core persistence.
Set<TEntity>()Generic access to a DbSet<TEntity>.
LoadAnonymizerRules()Anonymization rules used by database exports.

Change tracking​

To enable the EF Core audit trail, the context inherits from an LgApplicationTrackedChangeDbContext... variant. Changes are written to the LgTrackedChanges table for tracked entities.

ApplicationDbContext.cs
public class ApplicationDbContext : LgApplicationTrackedChangeDbContextGuid<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor)
: base(options, httpContextAccessor)
{ }
}

Tracking is driven by the EnableTrackedChangeAttribute and TrackedChangeAttribute attributes. The tracked variants also add SaveChangesAsync(string actionDetails, CancellationToken cancellationToken = default) and the TrackedChangeDisabled property.

Tracked entities

Do not add a DbSet<TrackedChange> or DbSet<TrackedChangeGuid> manually: the LgApplicationTrackedChangeDbContext<TUser> and LgApplicationTrackedChangeDbContextGuid<TUser> contexts already declare the audit entity.

Model conventions​

ElementConvention
Entity classSingular name.
DbSetPlural name.
Primary keyNever nullable.
StringDefine a maximum length.
BooleanIs prefix.
DateDate suffix.
Navigationvirtual property.
Migration snapshotNever edit ApplicationDbContextModelSnapshot.cs by hand.

See also​