Efcore: Improve "Unable to determine the relationship" error when navigation is pointing to keyless entityType

Created on 30 Sep 2019  路  6Comments  路  Source: dotnet/efcore

After upgrading my Entity Framework Core 2 project to Core 3, I am getting an Unable to determine the relationship represented by navigation property from one of my data context relationships. I have fully configured the relationship, but I cannot get rid of this error.

The relationship links an entity (EfTextUnit) with a composite PK (2 integers, named DocumentId and UnitNumber) to a collection of another entity (EfInputCharAttribute; 1-to-many):

public class EfTextUnit
{
    public int DocumentId { get; set; }
    public int UnitNumber { get; set; }
    // ...
    public List<EfInputCharAttribute> Attributes { get; set; }
    // ...
}

public class EfInputCharAttribute : InputCharAttribute
{
    public int Id { get; set; }
    public int DocumentId { get; set; }
    public int UnitNumber { get; set; }
    public EfTextUnit TextUnit { get; set; }
}

The data context configures these entities via fluent API:

// ...
public DbSet<EfTextUnit> TextUnits { get; set; }
public DbSet<EfInputCharAttribute> TextUnitAttributes { get; set; }
// ...

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.Entity<EfTextUnit>().ToTable("TextUnit");
    builder.Entity<EfTextUnit>()
        .HasKey(u => new { u.DocumentId, u.UnitNumber });
    builder.Entity<EfTextUnit>().Property(u => u.DocumentId)
        .IsRequired();
    builder.Entity<EfTextUnit>().Property(u => u.UnitNumber)
        .HasColumnName("Number")
        .IsRequired();
    // ...

    // text unit HAS MANY text unit attributes
    builder.Entity<EfTextUnit>()
        .HasMany(u => u.Attributes)
        .WithOne(a => a.TextUnit)
        .HasForeignKey(a => new { a.DocumentId, a.UnitNumber });

    // text unit HAS MANY snapshots
    builder.Entity<EfTextUnit>()
        .HasMany(u => u.Snapshots)
        .WithOne(s => s.TextUnit)
        .HasForeignKey(s => new { s.DocumentId, s.UnitNumber });

    // text unit HAS MANY observations
    builder.Entity<EfTextUnit>()
        .HasMany(u => u.Observations)
        .WithOne(o => o.TextUnit)
        .HasForeignKey(o => new { o.DocumentId, o.UnitNumber });

    // text unit attributes
    builder.Entity<EfInputCharAttribute>().ToTable("TextUnitAttribute");
    builder.Entity<EfInputCharAttribute>().Property(p => p.Id)
        .IsRequired()
        .UseSqlServerIdentityColumn();
     // .UseIdentityColumn(); USE THIS AFTER UPGRADING TO 3
    // ...
    builder.Entity<EfInputCharAttribute>().Property(u => u.DocumentId)
        .IsRequired();
    builder.Entity<EfInputCharAttribute>().Property(u => u.UnitNumber)
        .IsRequired();

    // text unit attribute HAS ONE text unit
    builder.Entity<EfInputCharAttribute>()
        .HasOne(a => a.TextUnit)
        .WithMany(u => u.Attributes)
        .HasForeignKey(a => new { a.DocumentId, a.UnitNumber });
// ...
}

If I run this with EF Core 2, it works fine. If I upgrade the project references to EF Core 3, and adjust the code accordingly, i.e. just replace the old UseSqlServerIdentity() call with the new method name, UseIdentity(), I get this exception:

I tried to use annotations in the POCO objects, but nothing changes.

Steps to reproduce

You can download a minimalist repro solution, and follow its readme.

Here is the exception:

System.InvalidOperationException: Unable to determine the relationship represented by navigation property 'EfTextUnit.Attributes' of type 'List<EfInputCharAttribute>'. 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(IConventionModelBuilder modelBuilder, IConventionContext`1 context)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalized(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalized(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel()
   at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass5_0.<GetModel>b__1()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.get_Value()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder)
   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.Infrastructure.DatabaseFacade.get_Dependencies()
   at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureCreated()
   at RelationshipRepro.Program.Main(String[] args) in D:\Projects\Core20\Test\RelationshipRepro\RelationshipRepro\Program.cs:line 15

Further technical details

EF Core version: 3.0.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET Core 3.0
Operating system: Windows 10 1903
IDE: Visual Studio 2019 16.3.1

area-model-building customer-reported good first issue poachable punted-for-3.1 type-bug

All 6 comments

@Myrmex - This did not reproduce for me. I have used following code. Please modify following code (or post a runnable repro) which demonstrate the error you are getting.
```C#
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace EFSampleApp
{
public class Program
{
public static void Main(string[] args)
{
using (var db = new MyContext())
{
// Recreate database
db.Database.EnsureDeleted();
db.Database.EnsureCreated();

            // Seed database


            db.SaveChanges();
        }

        using (var db = new MyContext())
        {
            // Run queries
            var query = db.Blogs.ToList();
        }
        Console.WriteLine("Program finished.");
    }
}


public class MyContext : DbContext
{
    private static ILoggerFactory ContextLoggerFactory
        => LoggerFactory.Create(b =>
        {
            b
            .AddConsole()
            .AddFilter("", LogLevel.Debug);
        });

    // Declare DBSets
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // Select 1 provider
        optionsBuilder
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=_ModelApp;Trusted_Connection=True;Connect Timeout=5;ConnectRetryCount=0")
            //.UseSqlite("filename=_modelApp.db")
            //.UseInMemoryDatabase(databaseName: "_modelApp")
            //.UseCosmos("https://localhost:8081", @"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", "_ModelApp")
            .EnableSensitiveDataLogging()
            .UseLoggerFactory(ContextLoggerFactory);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Configure model
        modelBuilder.Entity<EfTextUnit>().ToTable("TextUnit");
        modelBuilder.Entity<EfTextUnit>()
            .HasKey(u => new { u.DocumentId, u.UnitNumber });
        modelBuilder.Entity<EfTextUnit>().Property(u => u.DocumentId)
            .IsRequired();
        modelBuilder.Entity<EfTextUnit>().Property(u => u.UnitNumber)
            .HasColumnName("Number")
            .IsRequired();
        // ...

        // text unit HAS MANY text unit attributes
        modelBuilder.Entity<EfTextUnit>()
            .HasMany(u => u.Attributes)
            .WithOne(a => a.TextUnit)
            .HasForeignKey(a => new { a.DocumentId, a.UnitNumber });

        // text unit attributes
        modelBuilder.Entity<EfInputCharAttribute>().ToTable("TextUnitAttribute");
        modelBuilder.Entity<EfInputCharAttribute>().Property(p => p.Id)
            .IsRequired()
            .UseIdentityColumn();
        // ...
        modelBuilder.Entity<EfInputCharAttribute>().Property(u => u.DocumentId)
            .IsRequired();
        modelBuilder.Entity<EfInputCharAttribute>().Property(u => u.UnitNumber)
            .IsRequired();

        // text unit attribute HAS ONE text unit
        modelBuilder.Entity<EfInputCharAttribute>()
            .HasOne(a => a.TextUnit)
            .WithMany(u => u.Attributes)
            .HasForeignKey(a => new { a.DocumentId, a.UnitNumber });
    }
}

public class Blog
{
    public int Id { get; set; }
}

public class EfTextUnit
{
    public int DocumentId { get; set; }
    public int UnitNumber { get; set; }
    // ...
    public List<EfInputCharAttribute> Attributes { get; set; }
    // ...
}

public class EfInputCharAttribute
{
    public int Id { get; set; }
    public int DocumentId { get; set; }
    public int UnitNumber { get; set; }
    public EfTextUnit TextUnit { get; set; }
}

}
```

Thank you, your code works, but the problem seems more complex, even if it only focuses on this specific relationship. You can find here a new repro.

I tested the old repro on my workstation, and it raised the exception. Then I moved it to another machine, and started the EF 2 version: it worked (as expected from version 2) and created the database. I then upgraded it to EF 3: it worked, too, and this was surprising. I then removed the database to force a new creation, and it worked again. I copied the whole solution to the original machine, and yet it raised the same exception again. So I have the same code which works in a machine while it raises this exception in another one.

The only relevant difference between these machines seems the SQL Server version: the original one (where it raises exceptions) has CU11, the other one has CU14. I then replaced CU11 with CU16 in the original machine, but it kept raising the exception (these are all Docker Linux images, and I had issues in installing CU14 with a shared volume on a Windows host, while CU16 worked fine).

So I tried to recreate the repro by keeping more properties from the datacontext. It is the one in the link above. In both my machines now this raises the same exception, identical as the one posted before. Could you please try the new repro to see if this happens also to you? Just download, compile, and run. It is already at EF Core version 3.

Thanks again, that's puzzling...

Found the error
modelBuilder.Entity<EfInputCharAttribute>().HasNoKey();
You are configuring EfInputCharAttribute as keyless entity. Hence convention removed the explicit relationship and the navigation became not used in EF model. Hence you got exception that unable to determine relationship.
Regular entity type cannot have navigations to keyless entity types. See documentation for more details.

Repurposing this issue to improve exception message.

Thank you very much, I was totally misled by the error message. The error cropped out right because when migrating I changed a DbQuery to a DbSet; this, in turn, required me to add the HasNoKey, without realizing its consequences. Given the intricacies of the model, I switched from query-only DbSet to Dapper, thus removing the no-key requirement, and this did it in my scenario.

Reopening to track improving the exception message as above.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

iberodev picture iberodev  路  3Comments

leak picture leak  路  3Comments

miguelhrocha picture miguelhrocha  路  3Comments

bgribaudo picture bgribaudo  路  3Comments

ghost picture ghost  路  3Comments