Skip to content

Table Mapping

The MapToTable() extension method on Migrator converts an EF Core IEntityType into a Weasel ITable. This is the core of the EF Core integration -- it reads EF Core's metadata and produces a fully defined Weasel table object that participates in delta detection and DDL generation.

The guiding principle: the schema Weasel creates for a DbContext is the schema EF Core's own migration system would create, verified at the database-catalog level by a schema-parity test suite for PostgreSQL and SQL Server.

What Gets Mapped

EF Core MetadataWeasel Table Property
Table name and schemaITable.Identifier
Column name, type, nullabilityITableColumn with type, AllowNulls (TPH-aware via IsColumnNullable)
Max length, precision, column type annotationsColumn type string
HasDefaultValueSql(...)ITableColumn.DefaultExpression
HasDefaultValue(literal)DefaultExpression rendered with the provider's own SQL literal generator
Identity / serial value generationITableColumn.IsAutoNumber (IDENTITY, GENERATED BY DEFAULT AS IDENTITY, ...)
Computed columns (HasComputedColumnSql)ITableColumn.ComputedExpression + ComputedColumnIsStored
Primary key and constraint namePrimary key columns + PrimaryKeyName, exact casing preserved
Foreign keys with delete behaviorITable.AddForeignKey() with CascadeAction (Client* behaviors emit no ON DELETE clause, matching EF)
Indexes: HasIndex (unique, filtered, composite, INCLUDE), EF's conventional IX_* FK indexesITable.AddIndex() / ITableIndex
Npgsql HasMethod("gin") and friendsITableIndex.Method
Alternate keys (HasAlternateKey)Unique indexes
Check constraints (ToTable(t => t.HasCheckConstraint(...)))ITable.AddCheckConstraint()
Model sequences (HasSequence, UseHiLo, UseSequence)Weasel sequences via GetSchemaObjectsForMigration
JSON columns via OwnsOne().ToJson()Column with jsonb type (see JSON Columns)

Basic Usage

cs
var migrator = new PostgresqlMigrator(); // or SqlServerMigrator
using var context = dbContext;

foreach (var entityType in DbContextExtensions.GetEntityTypesForMigration(context))
{
    var table = migrator.MapToTable(entityType);
    // table is now a Weasel ITable with full schema definition
}

snippet source | anchor

To include the sequences declared on the model (from UseHiLo, UseSequence, or HasSequence) along with the tables, use GetSchemaObjectsForMigration. Sequences are returned first so column defaults that reference them (NEXT VALUE FOR ...) are valid when the tables are created:

cs
var migrator = new PostgresqlMigrator(); // or SqlServerMigrator
using var context = dbContext;

// Sequences declared on the model (HasSequence, UseHiLo, UseSequence)
// followed by the mapped tables, in dependency order
var schemaObjects = DbContextExtensions.GetSchemaObjectsForMigration(context, migrator);

snippet source | anchor

CreateMigrationAsync() uses GetSchemaObjectsForMigration internally, so HiLo and sequence-keyed models migrate correctly out of the box. Providers without sequences (MySQL, SQLite) simply contribute tables.

Identifier Casing

EF Core migrations emit quoted, case-sensitive identifiers ("BlogId", PK_Blogs, IX_Posts_BlogId). The mapper preserves that exact casing by setting ITable.PreserveIdentifierCase, and the case-folding providers (PostgreSQL, Oracle) quote identifiers in generated DDL so a Weasel-created schema is byte-for-byte usable by the EF Core runtime. Delta detection compares identifiers case-insensitively in both directions, so it makes no difference whether a schema was created by EF Core (quoted PascalCase) or by an older Weasel version (folded lowercase).

Entity Type Filtering

GetEntityTypesForMigration() applies several filters before returning entity types:

  • Excluded from migrations -- Entity types marked with ExcludeFromMigrations() are skipped.
  • No table name -- Entity types without a mapped table (e.g., keyless query types) are skipped.
  • Owned types sharing the owner's table -- Entity types configured via OwnsOne() without ToTable() (table splitting) or with .ToJson() do not get their own table; their columns (Nav_Prop style) or JSON container column are folded into the owner's table definition instead.
  • Owned types with their own table -- OwnsOne(...).ToTable(...) and OwnsMany(...) entity types are included: they map to real tables (with the PK-as-FK / composite-key shapes EF creates for them).

TPH (Table Per Hierarchy) Handling

When multiple entity types in a TPH hierarchy share the same table, only the root entity type produces a Weasel table. However, columns, foreign keys, indexes, and check constraints from all derived types in the hierarchy are included in that table definition. Required properties of derived types map to nullable columns, exactly as EF Core's relational model dictates.

TPT hierarchies map each type to its own table, with the derived tables keyed by a primary key that is also a foreign key to the base table -- and no identity on the derived keys, since their values come from the base row.

Foreign Key Dependencies and Topological Sorting

Entity types are topologically sorted by foreign key relationships using Kahn's algorithm. This ensures that when DDL is generated, referenced tables are created before the tables that reference them. If a circular dependency is detected, the original order is preserved as a fallback.

Row-internal linking foreign keys -- an owned type sharing its owner's table and key -- are skipped, exactly as EF Core migrations skip them.

Schema Resolution

The table schema is resolved as follows:

  1. If the entity type has an explicit schema via .ToTable("name", "schema"), that schema is used.
  2. Otherwise, the Migrator.DefaultSchemaName is used (e.g., public for PostgreSQL, dbo for SQL Server).

Column Drift Detection

By default, Weasel's delta detection treats column defaults and nullability as write-once: they are applied when a table is created but changing them later does not produce a migration. The opt-in ITable.DetectColumnDrift flag adds default-expression and nullability comparison for otherwise-matching columns, emitting ALTER COLUMN corrections.

The flag is deliberately not enabled by the EF Core mapper: default expressions are compared textually after canonicalization, and literal formats the database rewrites non-trivially (notably datetime literals) can produce perpetual false-positive migrations. Enable it per table -- e.g. through the mapped ITable instances -- when your defaults are simple numeric / string / boolean literals or stable function calls like now().

Known Limitations

  • Per-column descending sort in indexes is not expressible through the provider-neutral seam; set the provider's SortOrder on the concrete IndexDefinition for whole-index descending.
  • Npgsql identity-ALWAYS columns are created as GENERATED BY DEFAULT (more permissive; EF inserts behave identically).
  • Alternate keys are created as unique indexes rather than unique constraints -- functionally equivalent, including as foreign key targets.
  • Check constraints participate in delta detection conservatively: only the constraints the model declares are compared, and constraints Weasel doesn't know about are never dropped.

Released under the MIT License.