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.

EF Core adds an Id column for foreign key relation although foreign key is provided explicitely

See original GitHub issue

EF Core adds a column EntityDId for a foreign key relation where a specific key D_ID is specified. When querying the set of the objects with the foreign key relation, the SQL query contains both columns and fails, because the auto-assigned one EntityDId does not exist.

Include your code

.NET version: 5.0.302
EF Core Version: 5.0.13 + SQL Server DB provider with same version OS: Windows 10 1909 ODE: Visual Studio 2019 (version 16.10.4)

Repro done using ASP.NET Core 5 as our real project uses it, but can probably be done in a console application, too.
Nullable reference types are enabled.

Code for reproduction of the bug:

Models.cs

using System;
using System.Collections.Generic;

namespace Database
{
    public record ThreeWayEntity(bool IsDefault, bool IsReadOnly)
    {
        public EntityA EntityA { get; init; } = null!;
        public EntityB EntityB { get; init; } = null!;
        public EntityC EntityC { get; init; } = null!;
    }
    public record EntityA(int Id, string Name, string SomeData, bool IsAValue);
    public record EntityB(int Id, string Name, string LongName, string LongNameAlt, bool IsOk)
    {
        internal int? DefaultEntityDId { get; init; }
        public EntityD? DefaultEntityD { get; init; }
    }
    public record EntityC(int Id, string Name, string SomeValue, string AnotherValue);
    public record EntityD(int Id, string Identifier, string Name, bool IsOk, bool IsRelevant, DateTime? ADate)
    {
        internal int DifferentId { get; init; }

        internal int EntityAId { get; init; }
        public EntityA EntityA { get; init; } = null!;

        public List<EntityD> SubDs { get; init; } = new();
        public List<EntityB> AssignedBs { get; init; } = new();

        internal List<EntityB> BsWithThisDefaultD { get; init; } = new();
    }
}

ReproContext.cs

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Database
{
    public class ReproContext : DbContext
    {
        public ReproContext(DbContextOptions options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<EntityB>(entity =>
            {
                entity.ToTable("Table_B", "dbo");

                entity.HasKey(e => e.Id);
                entity.Property(e => e.Id).HasColumnName("B_ID");
                entity.Property(e => e.Name).HasColumnName("Name").IsRequired();
                entity.Property(e => e.LongName).HasColumnName("LongName").IsRequired();
                entity.Property(e => e.LongNameAlt).HasColumnName("LongNameAlt").IsRequired();
                entity.Property(e => e.IsOk).HasColumnName("Ok").IsRequired();

                entity.Property(e => e.DefaultEntityDId).HasColumnName("D_ID");
                entity.HasOne(e => e.DefaultEntityD).WithMany(e => e.BsWithThisDefaultD).HasForeignKey(e => e.DefaultEntityDId);
            }); 
            
            modelBuilder.Entity<EntityA>(entity =>
            {
                entity.ToTable("Table_A", "differentSchema");

                entity.HasKey(e => e.Id);
                entity.Property(e => e.Id).HasColumnName("A_ID");
                entity.Property(e => e.Name).HasColumnName("Name").IsRequired();
                entity.Property(e => e.SomeData).HasColumnName("SomeData").IsRequired();
                entity.Property(e => e.IsAValue).HasColumnName("IAV").IsRequired();
            });


            modelBuilder.Entity<EntityD>(entity =>
            {
                entity.ToView("v_Ds", "a_schema");
                entity.HasKey(e => e.Id);

                entity.Property(e => e.Id).HasColumnName("ID").IsRequired();
                entity.Property(e => e.Identifier).HasColumnName("Nomenclature").IsRequired();
                entity.Property(e => e.Name).HasColumnName("Nome").IsRequired();
                entity.Property(e => e.IsOk).HasColumnName("IsOk").IsRequired();
                entity.Property(e => e.IsRelevant).HasColumnName("IsRelevantDate").IsRequired();
                entity.Property(e => e.ADate).HasColumnName("ADate");

                entity.Property(e => e.EntityAId).HasColumnName("A_ID").IsRequired();
                entity.HasOne(e => e.EntityA).WithMany().HasForeignKey(e => e.EntityAId);

                entity.Property(e => e.DifferentId).HasColumnName("Dif_ID").IsRequired();
            }); 
            
            modelBuilder.Entity<EntityC>(entity =>
            {
                entity.ToTable("Table_C", "schema");

                entity.HasKey(e => e.Id);
                entity.Property(e => e.Id).HasColumnName("EntityC_ID");
                entity.Property(e => e.Name).HasColumnName("Name").IsRequired();
                entity.Property(e => e.SomeValue).HasColumnName("SomeValue").IsRequired();
                entity.Property(e => e.AnotherValue).HasColumnName("Another_Value").IsRequired();
            });

            modelBuilder.Entity<ThreeWayEntity>(entity =>
            {
                entity.ToTable("ThreeWay", "dbo");

                entity.Property<int>("A_ID").HasColumnName("A_ID");
                entity.Property<int>("B_ID").HasColumnName("B_ID");
                entity.Property<int>("C_ID").HasColumnName("C_ID");

                entity.Property(e => e.IsDefault).HasColumnName("IsDefault").IsRequired();
                entity.Property(e => e.IsReadOnly).HasColumnName("IsReadOnly").IsRequired();

                entity.HasOne(e => e.EntityB).WithMany().HasForeignKey("B_ID").IsRequired();
                entity.HasOne(e => e.EntityA).WithMany().HasForeignKey("A_ID").IsRequired();
                entity.HasOne(e => e.EntityC).WithMany().HasForeignKey("C_ID").IsRequired();

                entity.HasKey("A_ID", "B_ID", "C_ID");
            });
        }


        public async Task<IEnumerable<ThreeWayEntity>> GetThreeWayEntities(int bId, int aId)
        {
            var query = Set<ThreeWayEntity>()
                .Include(b => b.EntityC)
                .Include(b => b.EntityB)
                .Include(b => b.EntityA)
                .Where(b => b.EntityA.Id == aId && b.EntityB.Id == bId);

            return await query
                .AsNoTracking()
                .ToListAsync();
        }
    }
}

Issue

When calling GetThreeWayEntities, EF generates the following query (added line breaks for better readability):

DECLARE @__aId_0 int = 1;
DECLARE @__bId_1 int = 1;

SELECT [t].[A_ID], [t].[B_ID], [t].[C_ID], [t].[IsDefault], [t].[IsReadOnly], 
    [t2].[EntityC_ID], [t2].[Another_Value], [t2].[Name], [t2].[SomeValue], 
    [t1].[B_ID], [t1].[D_ID], [t1].[EntityDId], [t1].[Ok], [t1].[LongName], [t1].[LongNameAlt], [t1].[Name], 
    [t0].[A_ID], [t0].[IAV], [t0].[Name], [t0].[SomeData]
FROM [dbo].[ThreeWay] AS [t]
INNER JOIN [differentSchema].[Table_A] AS [t0] ON [t].[A_ID] = [t0].[A_ID]
INNER JOIN [dbo].[Table_B] AS [t1] ON [t].[B_ID] = [t1].[B_ID]
INNER JOIN [schema].[Table_C] AS [t2] ON [t].[C_ID] = [t2].[EntityC_ID]
WHERE ([t0].[A_ID] = @__aId_0) AND ([t1].[B_ID] = @__bId_1)

As you can see, the query contains both columns D_ID and EntityDId. Why is the latter included in the query if I explicitely configured the foreign key to use the property/column D_ID?

Edit: As far as I can see the issue persists even if I directly query EntityB. EF adds the EntityDId property when configuring the One-to-many relation instead of using the specified property.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
AndriySvyrydcommented, Feb 2, 2022

I do have a follow-up question regarding conventions: Is there any easy way to disable the naming conventions of navigation properties and their columns, such that they have to be mapped explicitely?

I think you are looking for https://github.com/dotnet/efcore/blob/main/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs, make sure to remove it from every set

0reactions
backfromexilecommented, Jan 31, 2022

Alright, I can see why they may be less manageable than issues. Thanks for your answers!

Read more comments on GitHub >

github_iconTop Results From Across the Web

How do you create a foreign key relationship in EF Core to ...
We've migrated away from using an incrementing ID column on the User table to generating the IDs in the Resource table and then...
Read more >
Changing Foreign Keys and Navigations - EF Core
How to change relationships between entities by manipulating foreign keys and navigations.
Read more >
Foreign Key Constraint
The `FOREIGN KEY` constraint specifies a column can contain only values exactly matching existing values from the column it references.
Read more >
Setting a Nullable Foreign Key property to Null triggers ...
This worked fine in Entity Framework Core 5;. Removing .OnDelete(DeleteBehavior.Cascade) from either of the Configurations does not affect this ...
Read more >
Conventions in Entity Framework Core
EF Core will specify that the primary key column values are generated automatically by the database. Foreign Key. The convention for a foreign ......
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