Efcore: Improve exception message when [ForeignKey] attribute is overriden by Fluent API

Created on 10 Jul 2019  路  4Comments  路  Source: dotnet/efcore

When I use [ForeignKey] on a dependent entity, and the fluent API on the parent with a backing field to represent the inverse navigation the below exception is thrown.

Partially Works without [ForeignKey]:

If I remove the [ForeignKey("AttributeId")] from AttributeValue, the exception goes away, but the Attribute property is always null. It's never filled in when the entity is loaded.

Works by Convention:

If I completely drop all explicit configurations (Fluent API or Attributes) and let EF sort it out via convention, it works as expected. No exceptions, and the Attribute field on AttributeValue gets filled in when the entity is loaded.

Works if not using a backing field:

If I drop the use of a backing field, and set this up like the Blog/Post examples using explicit attributes (ie. [ForeignKey]) it also fills in the navigational property when retrieving the entity from the DB. Though, this defeats the purpose of being defensive with the AttributeValue collection.

Exception message:

'The relationship from 'AttributeValue.Attribute' to 'Attribute' with foreign key properties {'AttributeId' : int} cannot target the primary key {'Id' : int} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.'

Steps to reproduce

Below code causes this:

Attribute:

    public class Attribute
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; private set; }
        private HashSet<AttributeValue> _values;
        public IEnumerable<AttributeValue> Values => _values?.ToList();

        //[Snip]
}

AttributeValue:

```c#
public class AttributeValue : IEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; private set; }
public string Value { get; private set; }

    public int AttributeId { get; private set; }
    [ForeignKey("AttributeId")]
    public Attribute Attribute { get; private set; }

    //[Snip]

}


**AttributeConfiguration**:

```cs
    public class AttributeConfiguration : IEntityTypeConfiguration<Attribute>
    {
        public void Configure(EntityTypeBuilder<Attribute> builder)
        {
            builder.HasMany(x => x.Values)
                .WithOne()
                .HasForeignKey(x => x.AttributeId);

            builder.Metadata
                .FindNavigation(nameof(Attribute.Values))
                .SetPropertyAccessMode(PropertyAccessMode.Field);
        }
    }

Further technical details

EF Core version: 2.2.4
Database Provider: InMemory
Operating system: Server 2016
IDE: Visual Studio 2019

customer-reported type-bug

Most helpful comment

@douglasg14b The issue here is that this code:
```C#
public int AttributeId { get; private set; }
[ForeignKey("AttributeId")]
public Attribute Attribute { get; private set; }

tells EF that the navigation property `Attribute` should use the `AttributeId` property for the FK.

But then this code:
```C#
builder.HasMany(x => x.Values)
    .WithOne()
    .HasForeignKey(x => x.AttributeId);

tells EF that the same FK property should be used for a relationship that explicitly does not use that navigation property, since no navigation property is specified in the WithOne call.

Changing it to be consistent with the configuration supplied by the attributes:
C# builder.HasMany(x => x.Values) .WithOne(x => x.Attribute) .HasForeignKey(x => x.AttributeId);
makes this work.

Note for triage: the exception message is not very helpful here.

All 4 comments

This has a tentative relation to some of the code/conversation in this issue: https://github.com/aspnet/EntityFrameworkCore/issues/10800

@douglasg14b The issue here is that this code:
```C#
public int AttributeId { get; private set; }
[ForeignKey("AttributeId")]
public Attribute Attribute { get; private set; }

tells EF that the navigation property `Attribute` should use the `AttributeId` property for the FK.

But then this code:
```C#
builder.HasMany(x => x.Values)
    .WithOne()
    .HasForeignKey(x => x.AttributeId);

tells EF that the same FK property should be used for a relationship that explicitly does not use that navigation property, since no navigation property is specified in the WithOne call.

Changing it to be consistent with the configuration supplied by the attributes:
C# builder.HasMany(x => x.Values) .WithOne(x => x.Attribute) .HasForeignKey(x => x.AttributeId);
makes this work.

Note for triage: the exception message is not very helpful here.

@ajcvickers

Oh, complete PEBKAC on my end! Thanks for the clarification, I completely missed that.

I'll leave this open for comment on the error message, which could provide better clarification in an instance such as this.

Triage: Putting this on the backlog to make the exception message better.

Was this page helpful?
0 / 5 - 0 ratings