dotnet/efcore · error · InvalidOperationException

The check constraints '{checkConstraint1}' on '{entityType1}

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

Thrown by CheckConstraint.AreCompatible during model validation/finalization when two check constraints are mapped to the same name (on table-sharing entity types) but have different defining SQL. EF cannot emit a single database constraint satisfying both, so it rejects the conflict rather than silently picking one.

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 dbf9771522)

Solutions

  1. Make the SQL bodies identical (exact string match, including whitespace) for constraints that should share a name.
  2. Give each conflicting constraint a distinct explicit Name so they do not collapse onto one database constraint.
  3. Remove the redundant constraint from one of the sharing types if the semantics are equivalent.
  4. If genuinely different predicates are needed, accept that they cannot share a name and explicitly disambiguate.

Example fix

// before (TPH: base + derived share table, same name, different SQL)
modelBuilder.Entity<Person>().HasCheckConstraint("CK_Age", "Age > 0");
modelBuilder.Entity<Employee>().HasCheckConstraint("CK_Age", "Age >= 18");

// after (make SQL agree, or rename one)
modelBuilder.Entity<Person>().HasCheckConstraint("CK_Age", "Age >= 18");
// derived no longer redeclares; or:
modelBuilder.Entity<Employee>().HasCheckConstraint("CK_Employee_Age", "Age >= 18");
Defensive patterns

Strategy: validation

Validate before calling

// For entity types sharing a table, verify same-named constraints share SQL
foreach (var cc in entityType.GetCheckConstraints())
{
    foreach (var other in sharingTypes.SelectMany(t => t.GetCheckConstraints()))
    {
        if (cc.GetName(table) == other.GetName(table) && cc.Sql != other.Sql)
            throw new InvalidOperationException($"SQL mismatch for {cc.GetName(table)}");
    }
}

Prevention

When it happens

Trigger: Entity types that share a table (TPH hierarchies, table splitting, owned types mapped to the same table) each declare a HasCheckConstraint with the same resolved name but different SqlBody strings. Surfaced when the relational model is built and compatibility is checked with shouldThrow=true.

Common situations: TPH hierarchy where base and derived both add CK_X with subtly different SQL (whitespace, casing, or genuinely different predicates). Owned entity sharing the owner's table adds a constraint that collides by default-name with the owner's.

Related errors


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