question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Metadata: InverseProperty fails to resolve ambiguity while use KeyAttribute on PK

See original GitHub issue

I am opening a issue regarding the [Foreignkey] attribute This was using the Pomelo.MySQL driver, but it might also affect other drivers.

(origional issue) https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/324

Note (as described below) that it worked perfectly fine in EFCore 1.0 and 1.1 and broke in 2.0

Steps to reproduce

  1. Create a test application with the model below 1 targeting NetCoreApp 1.1 1 targeting NetCoreApp 2.0
  2. Run “dotnet restore”
  3. Run “dotnet build”
  4. Run “dotnet ef migrations add initial”

Model used

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace EFModelTest
{
    class Program
    {
        static void Main(string[] args) { }
    }

    public class User
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }

        [MaxLength(64)]
        public string Name { get; set; }

        [InverseProperty(nameof(Relation.AccountManager))]
        public virtual ICollection<Relation> AccountManagerRelations { get; set; }

        [InverseProperty(nameof(Relation.SalesManager))]
        public virtual ICollection<Relation> SalesManagerRelations { get; set; }
    }

    public class Relation
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public string Id { get; set; }

        [MaxLength(64)]
        public string Name { get; set; }

        public int? AccountManagerId { get; set; }

        [ForeignKey(nameof(AccountManagerId))]
        public virtual User AccountManager { get; set; }

        public int? SalesManagerId { get; set; }

        [ForeignKey(nameof(SalesManagerId))]
        public virtual User SalesManager { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }
		
        public DbSet<Relation> Relations { get; set; }
		
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder.UseMySql(@"User Id=root;Password=root;Host=localhost;Database=eftest;Persist Security Info=True;Convert Zero Datetime=True;");
    }
}

The issue

Using data attributes to add a foreign key fails in EFCore 2.0 when the classname is not equal to a property name.

For instance: Mapping a UserId as foreign key to a class called User using a property User => works fine Mapping a ManagerId as foreign key to a class called User using a property called Manager => Broken

Stacktrace EF MySQL 1.1

PS D:\Sourcecodes\EF Core MySQL\NET1.1> dotnet ef migrations add initial

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.39
Done. To undo this action, use 'ef migrations remove'

Migration model

.....
   migrationBuilder.CreateTable(
                name: "Relations",
                columns: table => new
                {
                    Id = table.Column<string>(nullable: false),
                    AccountManagerId = table.Column<int>(nullable: true),
                    Name = table.Column<string>(maxLength: 64, nullable: true),
                    SalesManagerId = table.Column<int>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Relations", x => x.Id);
                    table.ForeignKey(
                        name: "FK_Relations_Users_AccountManagerId",
                        column: x => x.AccountManagerId,
                        principalTable: "Users",
                        principalColumn: "UserId",
                        onDelete: ReferentialAction.Restrict);
                    table.ForeignKey(
                        name: "FK_Relations_Users_SalesManagerId",
                        column: x => x.SalesManagerId,
                        principalTable: "Users",
                        principalColumn: "UserId",
                        onDelete: ReferentialAction.Restrict);
                });

            migrationBuilder.CreateIndex(
                name: "IX_Relations_AccountManagerId",
                table: "Relations",
                column: "AccountManagerId");

            migrationBuilder.CreateIndex(
                name: "IX_Relations_SalesManagerId",
                table: "Relations",
                column: "SalesManagerId");

Stacktrace EF MySQL 2.0

PS D:\Sourcecodes\Model Scaffolding Error\NET2.0> dotnet ef migrations add initial
System.InvalidOperationException: Unable to determine the relationship represented by navigation property 'Relation.AccountManager' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.PropertyMappingValidationConvention.Apply(InternalModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelBuilt(InternalModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelBuilt(InternalModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.Validate()
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalModelBuilder.Validate()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass5_0.<GetModel>b__0(Object k)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_1(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactoryService(FactoryService factoryService, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.<>c__DisplayClass17_0.<RealizeService>b__0(ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.Design.Internal.DesignTimeServicesBuilder.<>c__DisplayClass6_0.<ConfigureContextServices>b__7(IServiceProvider _)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactoryService(FactoryService factoryService, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitTransient(TransientCallSite transientCallSite, ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.<>c__DisplayClass17_0.<RealizeService>b__0(ServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.EnsureServices(IServiceProvider services)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_1.<.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 property 'Relation.AccountManager' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

Workaround

Using the fluent api to add the constraint fixes the issue, its purely data attribute constraints that can’t be resolved.

Adding the below modelbuilder to the MyContext fixes the issue in EF Core 2.0 This was not required in 1.1

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
	base.OnModelCreating(modelBuilder);
	modelBuilder.Entity<Relation>(e =>
	{
	   e.HasOne(r => r.AccountManager).WithMany(u => u.AccountManagerRelations).HasForeignKey(r => r.AccountManagerId);
	   e.HasOne(r => r.SalesManager).WithMany(u => u.SalesManagerRelations).HasForeignKey(r => r.SalesManagerId);
	});
}

Output with workaround

PS D:\Sourcecodes\Model Scaffolding Error\NET2.0> dotnet ef migrations add initial
Done. To undo this action, use 'ef migrations remove'

Scaffolds just fine

Further technical details

MySQL version: 5.7.14 Operating system: Windows Server 2012 Pomelo.EntityFrameworkCore.MySql version: 1.1.2 & 2.0.0-preview2-10046

Other details about my project setup: Visual Studio 2017 Preview 4

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:3
  • Comments:20 (9 by maintainers)

github_iconTop GitHub Comments

6reactions
rjamesnwcommented, Aug 31, 2017

In case anyone needs it, here is a quick fix that works for me (not thoroughly tested for all scenarios, and by no means meant to be efficient for long term):

/// <summary>
/// Temporarily fixes a bug with InverseProperty attribute being ignored: https://github.com/aspnet/EntityFrameworkCore/issues/9180
/// </summary>
public static void InversePropertyPatchEF20<TContext>(ModelBuilder modelBuilder) where TContext : DbContext
{
   // (need a patch to fix the InverseProperty issue in EF 2.0)

   // ... get all 'DbSet<>' properties from the given context and extract the entity types ...

   var modelTypes = (from t in typeof(TContext).GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
                       where t.PropertyType.IsGenericType && t.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)
                       select t.PropertyType.GenericTypeArguments[0]);

   foreach (var modelType in modelTypes)
   {
       // ... find all properties with InversePropertyAttribute and handle it ...

       var ipProperties = (from p in modelType.GetProperties()
                           where p.GetCustomAttribute<InversePropertyAttribute>() != null && p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
                           select p);

       foreach (var prop in ipProperties)
       {
           var otherModelType = prop.PropertyType.GenericTypeArguments[0]; // (gets the ICollection<T> generic parameter type (T), which points to the entity model on the other end)
           var ipAttr = prop.GetCustomAttribute<InversePropertyAttribute>();
           modelBuilder.Entity(modelType).HasMany(otherModelType, prop.Name).WithOne(ipAttr.Property);
       }
   }
}

Requirements: Make sure you have the [InverseProperty(...)] attrbiute on your ICollection<T> navigational properties where needed (do not have this attribute both ends). Why on the collection? Because it’s easier to filter by looking for the generic collection ICollection<T> and hard code Many-To-One instead of trying to detect the reverse as well. When this is no longer needed, removing this method should be all you need to do and it will still work as it should.

Usage example:

public partial class MyDBContextType // : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        ... many-to-many fluent mappings for "map" types ...
        InversePropertyPatchEF20<MyDBContextType>(modelBuilder);
        base.OnModelCreating(modelBuilder);
    }
...

I have 75 tables so far (many more coming) in a complex weave of many-to-many mappings, and it works like a charm; now I can continue. 8)

2reactions
brunopessanhacommented, Oct 2, 2017

Did anyone also have a problem with EF Core not being able to distinguish between the two foreign keys when persisting the data in the database?

I have a similar scenario with two one-to-many relations between the same entities in my model but when I save my changes to the DB, the same foreing key is used in both columns. I am using SQL server.

As shown here: relations

I have created this simple project to show how to reproduce the issue.

public class User {

	[Key]
	public int Id { get; set; }

	[InverseProperty(nameof(UserRelation.Relation1))]
	public Collection<UserRelation> Relations1 { get; set; }

	[InverseProperty(nameof(UserRelation.Relation2))]
	public Collection<UserRelation> Relations2 { get; set; }


}

public class UserRelation {

	[Key]
	public int Id { get; set; }

	public int Relation1Id { get; set; }

	[ForeignKey(nameof(Relation1Id))]
	public User Relation1 { get; set; }

	public int Relation2Id{ get; set; }

	[ForeignKey(nameof(Relation2Id))]
	public User Relation2 { get; set; }

}

On my DbContext I also have:

protected override void OnModelCreating(ModelBuilder modelBuilder) {
	base.OnModelCreating(modelBuilder);
	modelBuilder.Entity<UserRelation>(e => {
		e.HasOne(r => r.Relation1).WithMany(u => u.Relations1).HasForeignKey(r => r.Relation1Id).OnDelete(DeleteBehavior.ClientSetNull);
		e.HasOne(r => r.Relation2).WithMany(u => u.Relations2).HasForeignKey(r => r.Relation2Id).OnDelete(DeleteBehavior.ClientSetNull);
	});
}

You can find the whole demo project attached. I’m using Visual Studio 2017 Preview 2 TestCoreApp.zip

Are there any workarounds available is this an known issue? It seems to be connected to this one, but not exactly the same problem.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Metadata: InverseProperty fails to resolve ambiguity while ...
I am opening a issue regarding the [Foreignkey] attribute This was using the Pomelo.MySQL driver, but it might also affect other drivers.
Read more >
Entity Framework Core can't resolve inverse properties ...
The cause of the issue is more trivial - EF Core 2.0 (regression) bug tracked by #9180 Metadata: InverseProperty fails to resolve ambiguity...
Read more >
DataAnnotations - InverseProperty Attribute in Code-First
Data Annotations - InverseProperty Attribute in EF 6 & EF Core. The InverseProperty attribute is used when two entities have more than one...
Read more >
dotnet/efcore 2.1.0-preview2-final on GitHub
(#9265); Metadata: InverseProperty fails to resolve ambiguity while use KeyAttribute on PK (#9180); Subquery.Any(predicateParameter) throws exception (#8019) ...
Read more >
Entity Framework - Quick Guide
Entity Framework can generate the necessary database commands for reading or writing data in the database and execute them for you. If you're...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found