dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.79k stars 3.19k forks source link

Microsoft.EntityFrameworkCore.Cosmos "/id" does not match after upgrade to EF 9 #35130

Closed skordesign closed 1 hour ago

skordesign commented 5 hours ago

Information

The issue happens after upgrade Microsoft.EntityFrameworkCore.Cosmos to 9.0.0. When downgrade to 8.0.10 the issue doesn't happen.

DbContextConfig

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Gateway>().ToContainer("Gateways").HasPartitionKey(f => f.Id);
    }

Error

When call EnsureCreatedAsync it throws this exception

Unhandled exception. System.ArgumentException: The requested partition key path '/id' does not match existing Container 'Gateways' with partition key path '/Id' (Parameter 'PartitionKey')
   at Microsoft.Azure.Cosmos.DatabaseCore.CreateContainerIfNotExistsAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions, ITrace trace, CancellationToken cancellationToken)
   at Microsoft.Azure.Cosmos.ClientContextCore.RunWithDiagnosticsHelperAsync[TResult](String containerName, String databaseName, OperationType operationType, ITrace trace, Func`2 task, Tuple`2 openTelemetry, RequestOptions requestOptions, Nullable`1 resourceType)     
   at Microsoft.Azure.Cosmos.ClientContextCore.OperationHelperWithRootTraceAsync[TResult](String operationName, String containerName, String databaseName, OperationType operationType, RequestOptions requestOptions, Func`2 task, Tuple`2 openTelemetry, TraceComponent traceComponent, TraceLevel traceLevel, Nullable`1 resourceType)
   at Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal.CosmosClientWrapper.CreateContainerIfNotExistsOnceAsync(DbContext _, ValueTuple`2 parametersTuple, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.<>c__DisplayClass30_0`2.<<ExecuteAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func`4 operation, Func`4 verifySucceeded, TState state, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func`4 operation, Func`4 verifySucceeded, TState state, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal.CosmosDatabaseCreator.EnsureCreatedAsync(CancellationToken cancellationToken)

Include provider and version information

EF Core version: 9.0.0 Database provider: Microsoft.EntityFrameworkCore.Cosmos Target framework: NET 9.0 Operating system: Windows, Linux IDE: VScode

JimmyAtSchotte commented 3 hours ago

I'm having this problem.

In version 8.x.x, the document structure included both Id and id properties, like so:

{
    "Id": "3cf7d6e8-2821-47e0-85e2-08dc6f5438a6",
    "id": "3cf7d6e8-2821-47e0-85e2-08dc6f5438a6",
    "...": "other properties"
}

However, starting with version 9.0.0, the Id property is no longer included in the serialized document:

{
    "id": "3cf7d6e8-2821-47e0-85e2-08dc6f5438a6",
    "...": "other properties"
}

This change specifically affects properties named Id, which are now excluded from the document. It's worth noting:

JimmyAtSchotte commented 2 hours ago

Found the change in this issue: #34179

Adding .HasShadowId() reverts to old behaviour.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Gateway>().ToContainer("Gateways").HasPartitionKey(f => f.Id).HasShadowId();
}
skordesign commented 1 hour ago

I got another error after add .HasShadowId()

System.InvalidOperationException: Unable to materialize entity instance of type 'Gateway'. No discriminators matched the discriminator value ''.
JimmyAtSchotte commented 1 hour ago

I also had .HasNoDiscriminator() since before in my ModelBuilder. TBH I dont recall why adding that. But that works in my context.

I'm on thin ice here, but maybe look into the new method of .HasRootDiscriminatorInJsonId() in your case?

roji commented 1 hour ago

Yes, this change was indeed made intentionally in 9.0: standard Cosmos modeling simply has a single lowercase id property, and so the EF primary key property (Id by convention) is now simply mapped to that, removing the needless previous duplication of having both an id and an Id property. This seems like something I missed when documenting the Cosmos breaking changes - we should remedy that - I've opened https://github.com/dotnet/EntityFramework.Docs/issues/4883 to track that.

To summarize, new projects should simply use the default behavior and have their .NET Id property mapped to the Cosmos id property; existing projects are also encouraged to migrate if possible, but can use HasShadowId() to make the EF provider revert to the previous behavior.

@skordesign your error regarding the discriminator value is likely related to another change we made in 9.0, where the discriminator has been renamed from Discriminator to the more standard $type (see breaking change note); this change can also be reverted. See also the 9.0 release notes for Cosmos for more background on these changes. If you're still having trouble please open a new issue with a minimal, runnable repro so that we can understand what you're doing and help.