dotnet/efcore · error · InvalidOperationException

Entity type '{entityType}' doesn't contain a property mapped

Error message

Entity type '{entityType}' doesn't contain a property mapped to the store-generated concurrency token column '{missingColumn}' which is used by another entity type sharing the table '{table}'. Add a store-generated property to '{entityType}' which is mapped to the same column; it may be in shadow state.

What it means

When entity types share a table, a store-generated concurrency token column (e.g. rowversion) must be mapped by every sharing type, because SQL Server generates a new token value on any update to the row regardless of which entity triggered it. If one entity omits the token property, an update through that entity would not receive the new token and the next optimistic-concurrency check on the other entity would fail. The validator at line 1442-1458 builds the set of missing token columns per entity and throws MissingConcurrencyColumn.

Source

Thrown at src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:1455

            if (missingConcurrencyTokens != null)
            {
                missingConcurrencyTokens.Clear();
                foreach (var (concurrencyColumn, concurrencyProperties) in concurrencyColumns!)
                {
                    if (TableSharingConcurrencyTokenConvention.IsConcurrencyTokenMissing(concurrencyProperties, entityType, mappedTypes))
                    {
                        missingConcurrencyTokens.Add(concurrencyColumn);
                    }
                }
            }

            ValidateCompatible(entityType, storeObject, propertyMappings, missingConcurrencyTokens, logger);

            if (missingConcurrencyTokens != null)
            {
                foreach (var concurrencyColumn in missingConcurrencyTokens)
                {
                    throw new InvalidOperationException(
                        RelationalStrings.MissingConcurrencyColumn(
                            entityType.DisplayName(), concurrencyColumn, storeObject.DisplayName()));
                }
            }
        }

        var columnOrders = new Dictionary<int, List<string>>();
        foreach (var property in propertyMappings.Values)
        {
            var columnOrder = property.GetColumnOrder(storeObject);
            if (!columnOrder.HasValue)
            {
                continue;
            }

            var columns = columnOrders.GetOrAddNew(columnOrder.Value);
            columns.Add(property.GetColumnName(storeObject)!);
        }

View on GitHub (pinned to dbf9771522)

Solutions

  1. Add a shadow or declared concurrency-token property to the dependent entity mapped to the same column: .Property<byte[]>("RowVersion").IsRowVersion().HasColumnName("RowVersion").
  2. Remove the concurrency token from the principal if optimistic concurrency is not needed for the shared table.
  3. Stop sharing the table (map the dependent to its own table) so the token scoping is per-table.

Example fix

// before
modelBuilder.Entity<Order>()
    .Property(o => o.RowVersion).IsRowVersion();
modelBuilder.Entity<Order>().OwnsOne(o => o.Details); // no token on Details

// after - add a shadow token property on the dependent mapped to the same column
modelBuilder.Entity<Order>()
    .OwnsOne(o => o.Details, d =>
    {
        d.Property<byte[]>("RowVersion").IsRowVersion().HasColumnName("RowVersion");
    });
Defensive patterns

Strategy: validation

Validate before calling

foreach (var et in modelBuilder.Model.GetEntityTypes())
{
    var table = et.GetTableName();
    if (table == null) continue;
    var storeObj = StoreObjectIdentifier.Table(table, et.GetSchema());
    // gather token columns used by any type sharing this table
    var tokenCols = modelBuilder.Model.GetEntityTypes()
        .Where(e => e.GetTableName() == table)
        .SelectMany(e => e.GetProperties())
        .Where(p => p.IsConcurrencyToken && (p.ValueGenerated & ValueGenerated.OnAddOrUpdate) != 0)
        .Select(p => p.GetColumnName(storeObj))
        .Where(n => n != null).Distinct().ToList();
    var ownCols = et.GetProperties().Select(p => p.GetColumnName(storeObj)).ToHashSet();
    var missing = tokenCols.Where(c => !ownCols.Contains(c)).ToList();
    if (missing.Count > 0)
        throw new InvalidOperationException(
            $"{et.Name} sharing table '{table}' is missing concurrency columns: {string.Join(", ", missing)}");
}

Prevention

When it happens

Trigger: An owned/table-splitting entity whose principal has a `[Timestamp]`/`.IsRowVersion()`/`.IsConcurrencyToken()` property, but the dependent entity does not declare a property mapped to the same concurrency column. Detected by TableSharingConcurrencyTokenConvention.IsConcurrencyTokenMissing.

Common situations: Adding a rowversion column to the principal after the dependent was already configured; the dependent was generated by scaffolding before the token existed; using table splitting and forgetting to mirror the token.

Related errors


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