efcore: Metadata: InverseProperty fails to resolve ambiguity while use KeyAttribute on PK
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
- 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
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 3
- Comments: 20 (9 by maintainers)
Commits related to this issue
- Don't remove FKs when the referenced key is removed. Run ForeignKeyPropertyDiscoveryConvention when the PK is changed. Fixes #9180 Fixes #11019 — committed to dotnet/efcore by AndriySvyryd 6 years ago
- Don't remove FKs when the referenced key is removed. Run ForeignKeyPropertyDiscoveryConvention when the PK is changed. Fixes #9180 Fixes #11019 — committed to dotnet/efcore by AndriySvyryd 6 years ago
- Don't remove FKs when the referenced key is removed. Run ForeignKeyPropertyDiscoveryConvention when the PK is changed. Fixes #9180 Fixes #11019 — committed to dotnet/efcore by AndriySvyryd 6 years ago
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.
@yanchenw These log messages do not indicate errors; they are messages out of the model building conventions mechanisms describing what was discovered about the model as it is being processed by the conventions. The log level for these messages will be bumped down to “debug” as part of #10966.
@brunopessanha - I downloaded the zip file and examined the code. Following code
Even though you are setting
Relation1Id
to different values you are still adding all thoseUserRelations
touser1.Relations1
collection. Once you addUserRelation
touser1.Relation1
collection, navigation fixup will kick in and it will setRelation1Id
(FK associated with navigation) to PK ofuser1
. Hence for all theUserRelations
theRelation1Id
has value ofuser1.Id
Sorry,
.ToTable(Type)
was my own custom extension method, forgot about that. I had a custom convention to deal with table names. Also, yes, all my entities have the table attribute, it was a quick and dirty fix for me.The code is now updated to work properly, and no longer requires the
Table
attribute. It checks theDbSet<>
properties on the context instead in order to get the entity model types. You probably don’t needToTable()
either since the EF should figure this out implicitly, so I removed it.@ajcvickers No, the fix would have a non-trivial risk of causing other regressions.