dotnet/efcore · error · InvalidOperationException
Both properties '{property1}' and '{property2}' on entity ty
Error message
Both properties '{property1}' and '{property2}' on entity type '{entityType}' are mapped to '{storeName}'. Map one of the properties to a different JSON property. What it means
Thrown by ValidateDatabaseProperties when two persisted properties on the same entity type map to the same JSON property name (GetJsonPropertyName). Cosmos stores entities as JSON, so two CLR properties collapsing to one JSON name would silently overwrite each other; the validator surfaces this at model validation.
Source
Thrown at src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs:521
/// <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>
protected virtual void ValidateDatabaseProperties(
IEntityType entityType,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
var properties = new Dictionary<string, IPropertyBase>();
foreach (var property in entityType.GetProperties().Where(x => x.IsPersisted()))
{
var jsonName = property.GetJsonPropertyName();
if (properties.TryGetValue(jsonName, out var otherProperty))
{
throw new InvalidOperationException(
CosmosStrings.JsonPropertyCollision(property.Name, otherProperty.Name, entityType.DisplayName(), jsonName));
}
properties[jsonName] = property;
}
foreach (var navigation in entityType.GetNavigations())
{
if (!navigation.IsEmbedded())
{
continue;
}
var jsonName = navigation.TargetEntityType.GetContainingPropertyName()!;
if (properties.TryGetValue(jsonName, out var otherProperty))
{
throw new InvalidOperationException(
CosmosStrings.JsonPropertyCollision(navigation.Name, otherProperty.Name, entityType.DisplayName(), jsonName));View on GitHub (pinned to dbf9771522)
Solutions
- Give each property a distinct ToJsonProperty value.
- Rename one of the conflicting CLR properties or exclude one from persistence (IsPersisted(false)/not mapped).
- Audit JSON names when introducing a new property to an existing entity.
Example fix
// before
modelBuilder.Entity<User>()
.Property(u => u.Id).ToJsonProperty("name");
modelBuilder.Entity<User>()
.Property(u => u.DisplayName).ToJsonProperty("name");
// after
modelBuilder.Entity<User>()
.Property(u => u.Id).ToJsonProperty("id");
modelBuilder.Entity<User>()
.Property(u => u.DisplayName).ToJsonProperty("name"); Defensive patterns
Strategy: validation
Validate before calling
using var ctx = new MyContext();
foreach (var e in ctx.Model.GetEntityTypes())
{
var names = e.GetProperties().Where(p => p.IsPersisted()).Select(p => p.GetJsonPropertyName()).ToList();
Debug.Assert(names.Count == names.Distinct().Count(), $"{e.Name} has colliding JSON property names");
} Try / catch
try { ctx.Model.GetModel(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("are mapped to") && ex.Message.Contains("JSON property"))
{
logger.LogError(ex, "Two properties collide on the same JSON name");
throw;
} Prevention
- Audit ToJsonProperty values whenever adding or renaming a property.
- Avoid name pairs that differ only by case, which can collapse in JSON.
- Add a test asserting all persisted JSON names per entity are distinct.
When it happens
Trigger: Two properties whose default or ToJsonProperty-configured names coincide, e.g. Id and id differing only by casing collapsing to the same name, or explicitly mapping two properties to the same ToJsonProperty value.
Common situations: Case-insensitive name collisions (e.g. 'Id'/'id'); renaming via ToJsonProperty to a value already used; inherited properties colliding with declared ones.
Related errors
- The partition key property '{property1}' on '{entityType1}'
- The time to live for analytical store was configured to '{tt
- The default time to live was configured to '{ttl1}' on '{ent
- The provisioned throughput was configured to '{throughput1}'
- The provisioned throughput was configured as manual on '{man
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/357fecddf71ab564.
Report an issue: GitHub.