Skip to content

SchemaKit: a public plugin contract, so packages contribute to the model differ and migrations SQL generator instead of replacing them #56

Open

Description

@CaffeinatedCoder

Problem

EF Core resolves IMigrationsModelDiffer and IMigrationsSqlGenerator as single services. A package that needs either one replaces it (ReplaceService, as this package does today). When two packages do, one silently wins (the last ReplaceService call), and the other package's declarations vanish from migrations without an error.

So this package can't be one of several migration-aware packages. That is the reason for the new name (#37): SchemaKit is the one owner of both services in the CodoMetis family, and everything else that needs them plugs in. "Schema" is the boundary. Whatever needs the differ or the migrations SQL generator belongs here or plugs in. Types, operators and query translation stay out.

Change

ROADMAP.md D10 (section 5.3) planned contributors as several DI registrations through an options extension, as an internal seam. Make it the public contract from 11.0.0:

  • A contributor interface (diff a model pair into operations, render an operation per provider) and a registration that adds contributors without touching the two EF services.

  • The built-in features (complex and expression indexes, exclusion constraints, typed filters, …) are contributors themselves, so the package's own features prove the contract, not only a sample.

  • Dependency direction: an ecosystem package ships an optional satellite that references the SchemaKit kernel, the way CodoMetis.ValueRanges.NodaTime references its core. SchemaKit references none of them. The first prospective consumer is the ltree support in the planned CodoMetis.Hierarchies package: trigger-maintained paths, path checks, and any index options Npgsql's HasMethod/HasOperators can't express. Its type and query translation never need the contract.

  • The contract is public API under the same compatibility promise as the declaration API. The wiring call is renamed along with it (UseSchemaKit(), ROADMAP.md 5.6).

  • A migration renders the same SQL whichever package version applies it. A migration stores operations, not SQL. The generator renders them when the migration is applied or scripted, so an operation rendered by whatever contributor version is restored today means something different from what was scaffolded. That matters most for an operation that installs a body: a trigger function (the Hierarchies paths above), a check, a SQL function. The contract fixes the rule:

    • every operation carries the contract version it was diffed under, and a contributor keeps a renderer for every version it has shipped, or
    • the operation carries its rendered SQL from scaffolding time, frozen in the migration file.

    Changing a body is a new version. The snapshot records the version, so upgrading the package produces a migration that moves the database to the new body, instead of an old migration quietly applying it. Objects whose meaning a running application depends on (a function a query calls) use versioned names, so instances on the old and the new version can run side by side during a rolling deployment.

Done when

  • A contributor written in a test project outside the package adds an operation that migrations add, GenerateCreateScript and the pending-changes check all see, on PostgreSQL and on SQL Server.
  • A migration scaffolded with one version of a test contributor, applied and scripted with the next version (whose body changed), renders the first version's SQL. The upgrade itself shows up as a pending model change.
  • The built-in features are contributors.
  • docs/ has a page on writing a contributor.
  • It ships in 11.0.0, because the contract can't change in a minor afterwards.

Activity

  1. added this to the 11.0.0 milestone on Oct 19, 2026
  2. added
    enhancementNew feature or request
    package/coreAffects EFCore.ComplexIndexes
    package/postgresqlAffects EFCore.ComplexIndexes.PostgreSQL
    package/sqlserverAffects EFCore.ComplexIndexes.SqlServer
    area/migrationsThe differs, snapshot format, EnsureCreated and design-time wiring
    on Oct 19, 2026
  3. CaffeinatedCoder commented on Oct 26, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Added a requirement: a migration renders the same SQL whichever package version applies it, through versioned rendering or SQL frozen at scaffolding time, plus the matching Done-when line. It came up while evaluating SQL functions that ValueRanges could install through a contributor (CaffeinatedCoder/CodoMetis.ValueRanges#479, option 2). It applies to every contributor that installs a body, Hierarchies' trigger functions first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/migrationsThe differs, snapshot format, EnsureCreated and design-time wiringenhancementNew feature or requestpackage/coreAffects EFCore.ComplexIndexespackage/postgresqlAffects EFCore.ComplexIndexes.PostgreSQLpackage/sqlserverAffects EFCore.ComplexIndexes.SqlServer

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions