Skip to main content

DbContext and Entity Configuration

Site Profile uses a specialized pattern for Entity Framework Core that allows multiple sites to share the same core logic while diverging in their database schemas.

Base DbContext Pattern

To share logic across sites, we use a base DbContext that contains shared entity configurations.

// In MyProject.Core/Infrastructure/OrderContextBase.cs
public abstract class OrderContextBase<TContext> : DbContext
where TContext : DbContext
{
protected OrderContextBase(DbContextOptions<TContext> options) : base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

// Apply shared configurations from the Core assembly
modelBuilder.ApplyConfigurationsFromAssembly(typeof(OrderContextBase<>).Assembly);

// Allow sites to add their own configurations
ConfigureSiteSpecific(modelBuilder);
}

protected abstract void ConfigureSiteSpecific(ModelBuilder modelBuilder);
}

Creating a Site DbContext

Each site must have a unique DbContext class. This class should be sealed and inherit from the base context.

// In MyProject.Sites.Alpha/AlphaOrderContext.cs
public sealed class AlphaOrderContext : OrderContextBase<AlphaOrderContext>
{
public AlphaOrderContext(DbContextOptions<AlphaOrderContext> options)
: base(options)
{
}

protected override void ConfigureSiteSpecific(ModelBuilder modelBuilder)
{
// Apply Alpha-specific overrides
modelBuilder.Entity<OrderDetailEntity>()
.Property(e => e.Name)
.HasMaxLength(300); // Override base 200 length
}
}

Entity Inheritance Patterns

Entities in a Site Profile project typically follow one of two patterns:

  1. Shared Entity: Use the same entity class across all sites, but use EF Core Fluent API to map it to different column names or constraints.
  2. Site-Specific Subclass: Create a subclass for a site if it requires additional properties that do not exist in other sites.
// Base Entity (Core)
public class OrderDetailBase
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
}

// Site-Specific Entity (Bravo Site)
public class OrderDetailBravo : OrderDetailBase
{
public string BravoTrackingRef { get; set; } = string.Empty; // Extra column
}

EF Configuration Override Patterns

Column Name Override

Use HasColumnName in the site-specific ConfigureSiteSpecific method to map properties to legacy or custom column names.

// Site Bravo maps 'BookingNo' to 'BRAVO_BKG_ID'
modelBuilder.Entity<OrderDetailEntity>()
.Property(e => e.BookingNo)
.HasColumnName("BRAVO_BKG_ID");

Column Constraint Override

Override lengths, nullability, or default values as needed per site.

// Site Alpha allows longer descriptions
modelBuilder.Entity<OrderDetailEntity>()
.Property(e => e.Description)
.HasMaxLength(2000);

Index Override

Sites can define their own indices to optimize for their specific data distributions.

// Site Bravo adds a unique index on ContainerNo
modelBuilder.Entity<OrderDetailEntity>()
.HasIndex(e => e.ContainerNo)
.IsUnique();

Attribute-Based Column Mapping ([SiteColumn])

Instead of overriding ConfigureSiteSpecific(), you can decorate entity properties with [SiteColumn] attributes:

public class BravoOrder
{
[SiteColumn(Name = "BOOKING_NUMBER", MaxLength = 25)]
public string? BookingNo { get; set; }
}

Then apply in the DbContext:

modelBuilder.ApplySiteColumnOverrides<BravoOrder>(SiteIds.BRAVO);

See Site Profile Attributes for full parameter reference.

Virtual Configuration Groups

To make base configurations more maintainable, use virtual methods to group columns that sites commonly override together.

// In MyProject.Core/EntityConfigurations/OrderDetailConfigBase.cs
public class OrderDetailConfigBase : IEntityTypeConfiguration<OrderDetailEntity>
{
public void Configure(EntityTypeBuilder<OrderDetailEntity> builder)
{
ConfigureCommonFields(builder);
ConfigureSiteSpecificFields(builder);
}

protected virtual void ConfigureCommonFields(EntityTypeBuilder<OrderDetailEntity> builder) { ... }

// Sites override this method to handle their specific column naming groups
protected virtual void ConfigureSiteSpecificFields(EntityTypeBuilder<OrderDetailEntity> builder) { ... }
}

AddSiteDbContext — Coexisting Safely

Standard EF Core registration (AddDbContext<T>) registers a non-generic DbContextOptions which can cause conflicts in multi-site environments. Muonroi provides AddSiteDbContext<T> to register only the generic DbContextOptions<T>, allowing multiple sites to coexist safely in the same container.

// Internally used by [GenerateSiteProfile]
services.AddSiteDbContext<AlphaOrderContext>();

Key Benefits:

  • Isolation: Sites do not leak their DB configurations to each other.
  • Connection Resolution: Connection strings are resolved per-request via ITenantConnectionStringFactory.
  • Autofac Compatibility: Avoids "last-registration-wins" issues with non-generic options.

Ecosystem Base Class: MDbContext

The Muonroi ecosystem provides MDbContext as a base class for standard tenant-scoped projects. It includes:

  • Tenant-scoped query filters — automatically applies WHERE TenantId = @current to all ITenantScoped entities
  • IMLog integration — structured logging via Muonroi's logging abstractions
  • Soft-delete supportIsDeleted filter applied globally

When to Use MDbContext vs Raw DbContext

ScenarioRecommended BaseReason
Standard tenant-scoped project (shared schema)MDbContextGet tenant filters, logging, soft-delete for free
Site Profile project (schemas diverge per site)Raw DbContextEach site's DbContext has different entity configurations; MDbContext tenant filters may conflict with site-level isolation
Aggregate/gateway project (no DB)NeitherUse SkipDbContextRegistration = true
// Standard multi-tenant project — use MDbContext
public class AppDbContext : MDbContext<AppDbContext>
{
// Tenant filters, IMLog, soft-delete all auto-configured
}

// Site Profile project — use raw DbContext base
public abstract class OrderContextBase<TContext> : DbContext
where TContext : DbContext
{
// Each site overrides ConfigureSiteSpecific() with its own column mappings
protected abstract void ConfigureSiteSpecific(ModelBuilder modelBuilder);
}
tip

If your project uses Site Profile and needs tenant isolation within each site, consider applying tenant filters manually in ConfigureSiteSpecific() rather than inheriting from MDbContext. This gives you full control over which entities get which filters.

EF and Dapper Column Sync Risk

Site Profile uses two independent mapping layers: EF Core (HasColumnName() in OnModelCreating) and Dapper (ISiteColumnMap.Column()). If these diverge, queries will silently return wrong data or fail at runtime.

The problem: You rename a column in EF Core's fluent configuration but forget to update the ISiteColumnMap override (or vice versa). EF queries work correctly, but Dapper queries use the old column name — producing silent NULL values or SQL errors.

Startup validation pattern — Add this check to a hosted service or startup routine to detect mismatches early:

// Run in dev/staging only (not production) for performance
var efProperties = context.Model.FindEntityType(typeof(MyEntity))!
.GetProperties()
.ToDictionary(p => p.Name, p => p.GetColumnName());

foreach (var (propName, efColumn) in efProperties)
{
string mapColumn = columnMap.Column(propName);
if (!string.Equals(efColumn, mapColumn, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException(
$"Column mismatch for {propName}: EF='{efColumn}', ISiteColumnMap='{mapColumn}'");
}

Run this for each site's DbContext + column map pair during application startup in non-production environments. This catches mismatches before they reach production.

Schema Validation at Startup

Planned feature — not yet implemented

Schema validation at startup is planned but not yet available in the current release. When implemented, it will check that the database schema matches the EF Core model for all registered site DbContexts, preventing runtime errors due to schema drift.

Source Files

  • samples/TestProject.Service/src/TestProject.Service.Core/Infrastructure/ContextBase.cs
  • samples/TestProject.Service/src/TestProject.Service.Core/Infrastructure/EntityConfigurations/OrderDetailConfigBase.cs
  • samples/TestProject.Service/src/TestProject.Service.Sites.Alpha/AlphaOrderContext.cs
  • samples/TestProject.Service/src/TestProject.Service.Sites.Bravo/BravoOrderContext.cs
  • src/Muonroi.Tenancy.SiteProfile.Web/SiteProfileDbContextExtensions.cs

Next Steps