dotnet / dotnet/efcore

Cycle detection when detecting value converter types over foreign keys throws InvalidOperationException

Open
#38,301 4 comments 0 reactions 1 assignee Claimed by @AndriySvyryd View on GitHub
customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

### Bug description

I have ended up with a rather complex/annoying set of relationships between 2 types (See the attached code).

I have tried reducing it further, but that doesn't seem possible without causing the issue to disappear.

If I explicitly set the `ProviderClrType` annotation on the `WorkspaceId` property, when the issue stops occuring.

If I don't use the multi-column (foreign)keys, then the issue does not occur.

The issue still occurs when specifying all the columns to be `int` rather than Guid. (Setting ProviderClrType still works to 'fix' the issue.)

The issue seems to occur on all versions between EFCore 8.0.0 up to and including EFCore version `11.0.0-preview.3.26207.106`. (I only have dotnet 11 preview 3 available in my package manager, not preview 4 yet, so I have not tested preview 4).

The issue seems to occur no matter the database provider. I have tested "InMemory", "Sqlite" and "Postgres".

### Your code

```csharp
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

await using var context = new ReproContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

public class ReproContext : DbContext
{
public DbSet Messages => Set();
public DbSet Sessions => Set();

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder
.UseInMemoryDatabase("Repro")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity(session =>
{
// session.Property(s => s.WorkspaceId).HasAnnotation(CoreAnnotationNames.ProviderClrType, typeof(Guid));
session.HasKey(s => new { s.WorkspaceId, s.Id });

session
.HasOne(s => s.ParentMessage)
.WithMany(m => m.ChildChatSessions)
.HasForeignKey(s => new { s.WorkspaceId, s.ParentMessageId })
.HasPrincipalKey(m => new {m.WorkspaceId, m.Id})
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);

session
.HasOne(s => s.ParentSession)
.WithMany(s => s.ChildSessions)
.HasForeignKey(s => new { s.WorkspaceId, s.ParentSessionId })
.HasPrincipalKey(s => new {s.WorkspaceId, s.Id})
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
});

modelBuilder.Entity(message =>
{
// message.Property(s => s.WorkspaceId).HasAnnotation(CoreAnnotationNames.ProviderClrType, typeof(Guid));
message.HasKey(m => new { m.WorkspaceId, m.Id });

message
.HasOne(m => m.Session)
.WithMany(s => s.Messages)
.HasForeignKey(m => new { m.WorkspaceId, m.SessionId })
.HasPrincipalKey(s => new { s.WorkspaceId, s.Id })
.OnDelete(DeleteBehavior.Restrict);

message
.HasOne(m => m.PreviousMessage)
.WithMany(m => m.FollowingMessages)
.HasForeignKey(m => new { m.WorkspaceId, m.PreviousMessageId })
.HasPrincipalKey(m => new { m.WorkspaceId, m.Id })
.OnDelete(DeleteBehavior.Restrict);
});
}
}

public class ChatMessage
{
public Guid WorkspaceId { get; set; }
public Guid Id { get; set; }

public Guid SessionId { get; set; }
public ChatSession Session { get; set; } = null!;

public Guid? PreviousMessageId { get; set; }
public ChatMessage? PreviousMessage { get; set; }

public ICollection FollowingMessages { get; set; } = null!;

public ICollection ChildChatSessions { get; set; } = null!;
}

public class ChatSession
{
public Guid WorkspaceId { get; set; }

public Guid Id { get; set; }

public Guid? ParentMessageId { get; set; }
public ChatMessage? ParentMessage { get; set; }

public Guid? ParentSessionId { get; set; }
public ChatSession? ParentSession { get; set; }

public ICollection ChildSessions { get; set; } = null!;

public ICollection Messages { get; set; } = [];
}
```

### Stack traces

```text
Unhandled exception. System.InvalidOperationException: A relationship cycle involving the property 'ChatMessage.WorkspaceId' was detected. This prevents Entity Framework from determining the correct configuration. Review the foreign keys defined on the property and the corresponding principal property and either remove one of them or specify 'ValueConverter' explicitly on one of the properties.
at Microsoft.EntityFrameworkCore.Metadata.Internal.Property.GetConversion(Boolean throwOnValueConverterConflict, Boolean throwOnProviderClrTypeConflict)
at Microsoft.EntityFrameworkCore.Metadata.Internal.Property.GetValueConverter()
at Microsoft.EntityFrameworkCore.Storage.TypeMappingInfo..ctor(IReadOnlyList`1 principals, Nullable`1 fallbackUnicode, Nullable`1 fallbackSize, Nullable`1 fallbackPrecision, Nullable`1 fallbackScale)
at Microsoft.EntityFrameworkCore.Storage.TypeMappingSource.FindMapping(IProperty property)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.ElementMappingConvention.g__Validate|4_0(IConventionTypeBase typeBase)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.ElementMappingConvention.ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext`1 context)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalizing(IConventionModelBuilder modelBuilder)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalizing(IConventionModelBuilder modelBuilder)
at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel()
at Microsoft.EntityFrameworkCore.Infrastructure.ModelRuntimeInitializer.Initialize(IModel model, Boolean designTime, IDiagnosticsLogger`1 validationLogger)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.b__8_4(IServiceProvider p)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure.get_Instance()
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService(IInfrastructure`1 accessor, Type serviceType)
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.get_Dependencies()
at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureDeletedAsync(CancellationToken cancellationToken)
at Program.$(String[] args) in /home/rasmus/projects/EfCoreReproduction/EfCoreReproduction/Program.cs:line 9
at Program.$(String[] args) in /home/rasmus/projects/EfCoreReproduction/EfCoreReproduction/Program.cs:line 10
at Program.(String[] args)
```

### Verbose output

```text

```

### EF Core version

10.0.8

### Database provider

Microsoft.EntityFrameworkCore.InMemory, Microsoft.EntityFrameworkCore.Sqlite, Npgsql.EntityFrameworkCore.PostgreSQL

### Target framework

.net 10

### Operating system

Nixos

### IDE

Commandline

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.