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 Metadata | Weasel Table Property |
|---|---|
| Table name and schema | ITable.Identifier |
| Column name, type, nullability | ITableColumn with type, AllowNulls (TPH-aware via IsColumnNullable) |
| Max length, precision, column type annotations | Column type string |
HasDefaultValueSql(...) | ITableColumn.DefaultExpression |
HasDefaultValue(literal) | DefaultExpression rendered with the provider's own SQL literal generator |
| Identity / serial value generation | ITableColumn.IsAutoNumber (IDENTITY, GENERATED BY DEFAULT AS IDENTITY, ...) |
Computed columns (HasComputedColumnSql) | ITableColumn.ComputedExpression + ComputedColumnIsStored |
| Primary key and constraint name | Primary key columns + PrimaryKeyName, exact casing preserved |
| Foreign keys with delete behavior | ITable.AddForeignKey() with CascadeAction (Client* behaviors emit no ON DELETE clause, matching EF) |
Indexes: HasIndex (unique, filtered, composite, INCLUDE), EF's conventional IX_* FK indexes | ITable.AddIndex() / ITableIndex |
Npgsql HasMethod("gin") and friends | ITableIndex.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
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
}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:
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);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()withoutToTable()(table splitting) or with.ToJson()do not get their own table; their columns (Nav_Propstyle) or JSON container column are folded into the owner's table definition instead. - Owned types with their own table --
OwnsOne(...).ToTable(...)andOwnsMany(...)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:
- If the entity type has an explicit schema via
.ToTable("name", "schema"), that schema is used. - Otherwise, the
Migrator.DefaultSchemaNameis used (e.g.,publicfor PostgreSQL,dbofor 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
SortOrderon the concreteIndexDefinitionfor whole-index descending. - Npgsql identity-
ALWAYScolumns are created asGENERATED 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.
