Metadata: InverseProperty fails to resolve ambiguity while use KeyAttribute on PK
See original GitHub issueI 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
- Create a test application with the model below 1 targeting NetCoreApp 1.1 1 targeting NetCoreApp 2.0
- Run “dotnet restore”
- Run “dotnet build”
- 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:
- Created 6 years ago
- Reactions:3
- Comments:20 (9 by maintainers)
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):
Requirements: Make sure you have the
[InverseProperty(...)]
attrbiute on yourICollection<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 collectionICollection<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:
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)
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:
I have created this simple project to show how to reproduce the issue.
On my DbContext I also have:
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.