EF Core adds an Id column for foreign key relation although foreign key is provided explicitely
See original GitHub issueEF 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:
- Created 2 years ago
- Comments:5 (3 by maintainers)
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
Alright, I can see why they may be less manageable than issues. Thanks for your answers!