dotnet/efcore · error · InvalidOperationException

The check constraints

Error message

The check constraints '{checkConstraint1}' on '{entityType1}' and '{checkConstraint2}' on '{entityType2}' are both mapped to '{checkConstraintName}', but with different defining SQL.

What it means

Two check constraints across entity types are mapped to the same database check name (typically because both entity types map to the same table, e.g. TPH or split-table scenarios), but their defining SQL differs. CheckConstraint.AreCompatible throws when the SQL strings are unequal and shouldThrow is set during model validation.

Solutions

  1. Open both check constraints named in the message and compare their SQL character-by-character.
  2. Make the SQL strings identical (normalize whitespace, casing, identifiers) so the constraint is unambiguous.
  3. If the constraints are genuinely different, give them distinct constraint names so each maps independently.

Example fix

// before
modelBuilder.Entity<Order>().HasCheckConstraint("ck_amount", "Amount > 0");
modelBuilder.Entity<OrderLine>()  // mapped to same table via splitting
    .HasCheckConstraint("ck_amount", "[Amount] > 0");

// after (normalize SQL text)
modelBuilder.Entity<Order>().HasCheckConstraint("ck_amount", "[Amount] > 0");
modelBuilder.Entity<OrderLine>()
    .HasCheckConstraint("ck_amount", "[Amount] > 0");
Defensive patterns

Strategy: validation

Validate before calling

// Before finalizing, verify every shared check-constraint name has identical SQL across entity types mapped to the same table
foreach (var et in modelBuilder.Model.GetEntityTypes())
{
    foreach (var ck in et.GetDeclaredCheckConstraints())
    {
        foreach (var other in modelBuilder.Model.GetEntityTypes().Where(e => e != et))
        {
            var dup = other.FindCheckConstraint(ck.ModelName);
            if (dup is not null
                && et.GetSchemaQualifiedTableName() == other.GetSchemaQualifiedTableName()
                && ck.Sql != dup.Sql)
            {
                throw new InvalidOperationException($"Check '{ck.ModelName}' SQL differs between {et.Name} and {other.Name}.");
            }
        }
    }
}

Prevention

When it happens

Trigger: Two entity types mapped to the same table each declare a check constraint resolving to the same constraint name but with different SQL text; common in TPT/TPH or entity splitting. Fires during RelationalModelValidator / convention passes with shouldThrow=true.

Common situations: Whitespace/casing differences that look identical to a human but compare unequal; intentional reuse of a name across two entities with divergent intent; legacy schemas where two developers added different constraints to the same logical table.

Related errors


AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11). Data as JSON: /api/errors/75ec2b523b237b3c. Report an issue: GitHub.

Appendix: source

Thrown at src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs:198

        ((InternalCheckConstraintBuilder)existingCheckConstraint.Builder).MergeAnnotationsFrom(
            (CheckConstraint)detachedCheckConstraint);
    }

    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public static bool AreCompatible(
        IReadOnlyCheckConstraint checkConstraint,
        IReadOnlyCheckConstraint duplicateCheckConstraint,
        in StoreObjectIdentifier storeObject,
        bool shouldThrow)
        => checkConstraint.Sql == duplicateCheckConstraint.Sql
            || (shouldThrow
                ? throw new InvalidOperationException(
                    RelationalStrings.DuplicateCheckConstraintSqlMismatch(
                        checkConstraint.ModelName,
                        checkConstraint.EntityType.DisplayName(),
                        duplicateCheckConstraint.ModelName,
                        duplicateCheckConstraint.EntityType.DisplayName(),
                        checkConstraint.GetName(storeObject)))
                : false);

    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public virtual InternalCheckConstraintBuilder Builder
    {
        [DebuggerStepThrough]
        get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel(ModelName));

View on GitHub (pinned to 3a2006ef56)