首页 > 解决方案 > Double Relation One to Many (different Property same Class), Unable to determine the relationship represented by navigation, C#

问题描述

I want to establish double relation using List between NxProperty custom class and NxLog custom class.

public class NxLog
{
    public string NxLogId { get; set; }
    public string NxWorkspaceId { get; set; }
    public string NxConversationId { get; set; }
    public NxConversation NxConversation { get; set; }
    public List<NxOutputText> NxOutputTexts { get; set; }
    public List<NxProperty> RequestProperties { get; set; }     //1st Relation
    public List<NxProperty> ResponseProperties { get; set; }    //2nd Relation
}

public class NxProperty
{
    public string NxPropertyId { get; set; }
    public string NxWorkspaceId { get; set; }
    public string NxConversationId { get; set; }
    public string NxLogId { get; set; }
    public NxLog NxLog { get; set; }
    public JToken Value { get; set; }
}

In the DbContext daughter class I have:

class CustomDbContext:DbContext
{
    DbSet<NxWorkspace> Workspaces { get; set; }
    DbSet<NxConversation> Conversations { get; set; }
    DbSet<NxLog> Logs { get; set; }
    DbSet<NxOutputText> OutputTexts { get; set; }
    DbSet<NxProperty> RequestProperties { get; set; }
    DbSet<NxProperty> ResponseProperties { get; set; }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<NxWorkspace>().HasKey(w => w.NxWorkspaceId);
        modelBuilder.Entity<NxConversation>().HasKey(c => new { c.NxWorkspaceId, c.NxConversationId });
        modelBuilder.Entity<NxLog>().HasKey(l => new { l.NxWorkspaceId, l.NxConversationId, l.NxLogId });
        modelBuilder.Entity<NxOutputText>().HasKey(o => new { o.NxWorkspaceId, o.NxConversationId, o.NxLogId, o.NxOutputTextId });
        modelBuilder.Entity<NxProperty>().HasKey(p => new { p.NxWorkspaceId, p.NxConversationId, p.NxLogId, p.NxPropertyId });
        
        modelBuilder.Entity<NxProperty>().Property(l => l.Value)
            .HasConversion<string>(
            o => JsonConvert.SerializeObject(o),
            d => JsonConvert.DeserializeObject<JToken>(d)
            );
            
        //Other steps
    }
}

But I get the Exception:

System.InvalidOperationException: Unable to determine the relationship represented by navigation 'NxLog.RequestProperties' of type 'List<NxProperty>'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidatePropertyMapping(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.SqlServer.Internal.SqlServerModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.ValidatingConvention.ProcessModelFinalized(IModel model)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalized(IModel model)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalized(IModel model)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel()
   at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_3(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 singletonCallSite, 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 singletonCallSite, 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__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, 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_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func`1 factory)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Unable to determine the relationship represented by navigation 'NxLog.RequestProperties' of type 'List<NxProperty>'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
PM>

I need to be sure that I have the right code.

I was trying with

modelBuilder.Entity<NxLog>()
    .HasMany(l => l.RequestProperties)
    .WithOne(p => p.NxLog).HasForeignKey(p => p.NxLogId);

modelBuilder.Entity<NxLog>()
    .HasMany(l => l.ResponseProperties)
    .WithOne(p => p.NxLog).HasForeignKey(p => p.NxLogId);

But I get...

Cannot create a relationship between 'NxLog.ResponseProperties' and 'NxProperty.NxLog' 
because a relationship already exists between 'NxLog.RequestProperties' and 'NxProperty.NxLog'. 
Navigation properties can only participate in a single relationship.
If you want to override an existing relationship call 'Ignore' on the navigation 
'NxProperty.NxLog' first in 'OnModelCreating'.

How I must to establish the relation in the OnModelCreating(ModelBuilder modelBuilder) method?

Can you share with me the code for modelBuilder.Entity<NxProperty>().HasMany(...)?

Thanks in advance

标签: c#entity-framework-coreone-to-manyinvalidoperationexceptionef-model-builder

解决方案


I had to look at like 9 times to understand your code. So first of all, having the same class represent multiple tables is just wrong in my honest opinion. If you want to handle them together create a base class for both and then create child classes. This is why you can't map them correctly, because the name is ambiguous. But I'm here to help.

  1. Step: Refactor your NxProperty class.
  • Make NxProperty an abstract class.
  • Create an empty class which inherits from this class called NxRequestProperty.
  • Create another empty class which inherits from this class called NxResponseProperty.

Example:

//NgProperty.cs
public abstract class NxProperty
{
    public string NxPropertyId { get; set; }
    public string NxWorkspaceId { get; set; }
    public string NxConversationId { get; set; }
    public string NxLogId { get; set; }
    public NxLog NxLog { get; set; }
    public JToken Value { get; set; }
}

// NxRequestProperty.cs
// using sealed is just my preference so other
// developers know not to inherit from it without
// asking questions, you can exclude it if you wish.
public sealed class NxRequestProperty : NxProperty {

}

// NxResponseProperty.cs
public sealed class NxResponseProperty : NxProperty {

}
  1. Step: Refactor your NxLog class. Use the concrete classes instead of the NxProperty abstract class.

Example:

// NxLog.cs
public sealed class NxLog
{
    public string NxLogId { get; set; }
    public string NxWorkspaceId { get; set; }
    public string NxConversationId { get; set; }

    public NxConversation NxConversation { get; set; }

    public List<NxOutputText> NxOutputTexts { get; set; }
    public List<NxRequestProperty> RequestProperties { get; set; }
    public List<NxResponseProperty> ResponseProperties { get; set; }
}
  1. Step: Refactor your CustomDbContext. You NxProperty class is now abstract and you should not use it as a DbSet (imho), let's refactor it.

Example:

// CustomDbContext.cs
public sealed class CustomDbContext : DbContext
{
    DbSet<NxWorkspace> Workspaces { get; set; }
    DbSet<NxConversation> Conversations { get; set; }
    DbSet<NxLog> Logs { get; set; }
    DbSet<NxOutputText> OutputTexts { get; set; }
    // Refactor starts here!
    DbSet<NxRequestProperty> RequestProperties { get; set; }
    DbSet<NxResponseProperty> ResponseProperties { get; set; }
    // Refactor ends here!    

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<NxWorkspace>().HasKey(w => w.NxWorkspaceId);
        modelBuilder.Entity<NxConversation>().HasKey(c => new { c.NxWorkspaceId, c.NxConversationId });
        modelBuilder.Entity<NxLog>().HasKey(l => new { l.NxWorkspaceId, l.NxConversationId, l.NxLogId });
        modelBuilder.Entity<NxOutputText>().HasKey(o => new { o.NxWorkspaceId, o.NxConversationId, o.NxLogId, o.NxOutputTextId });

        // Refactor starts here!
        modelBuilder.Entity<NxRequestProperty>().HasKey(p => new { p.NxWorkspaceId, p.NxConversationId, p.NxLogId, p.NxPropertyId });
        modelBuilder.Entity<NxResponseProperty>().HasKey(p => new { p.NxWorkspaceId, p.NxConversationId, p.NxLogId, p.NxPropertyId });
        
        modelBuilder.Entity<NxRequestProperty>().Property(l => l.Value)
            .HasConversion<string>(
            o => JsonConvert.SerializeObject(o),
            d => JsonConvert.DeserializeObject<JToken>(d)
        );
          
        modelBuilder.Entity<NxResponseProperty>().Property(l => l.Value)
            .HasConversion<string>(
            o => JsonConvert.SerializeObject(o),
            d => JsonConvert.DeserializeObject<JToken>(d)
        );
        // Refactor ends here!
        //Other steps
    }
}
  1. (and final) Step: I'm not sure whether EF Core could map the relations now, but I believe in an always-declare source code is a better source code, so here is how to map it. Due to having composite keys, you need to have the foreign keys as all the composite keys.

Example:

// CustomDbContext.cs -> inside the OnModelCreating method
modelBuilder.Entity<NxRequestProperty>
    .HasOne(p => p.NxLog)
    .WithMany(l => l.RequestProperties)
    .HasForeignKey(l => new { l.NxWorkspaceId, l.NxConversationId, l.NxLogId });

modelBuilder.Entity<NxResponseProperty>
    .HasOne(p => p.NxLog)
    .WithMany(l => l.ResponseProperties)
    .HasForeignKey(l => new { l.NxWorkspaceId, l.NxConversationId, l.NxLogId });

EDIT:

Looking at my answer I realized that maybe refactoring NxProperty to an interface is better (and calling it INxProperty). Both would work, but I think the interface is better suited for this.


推荐阅读