EF Core 7.0.8中SizeUnitMeasure/WeightUnitMeasure导航属性未填充问题
我在C#控制台应用中复现EF Core 7.0.8的一个问题,使用了简单模型。
查询代码:
using ConsoleApp1; using Microsoft.EntityFrameworkCore; using var db = new ProductContext(); Console.WriteLine("Get product df86dd7f-727d-462f-93e8-6a9caf577a42"); var query = db.Products .Include(_ => _.ProductModel) .Include(_ => _.SizeUnitMeasure) .Include(_ => _.WeightUnitMeasure) .Where(_ => _.ObjectId == Guid.Parse("df86dd7f-727d-462f-93e8-6a9caf577a42")); var queryString = query.ToQueryString(); var product = query.First(); Console.WriteLine($"Product name: {product.Name}");
ProductContext类代码:
public class ProductContext : DbContext { public DbSet<Product> Products { get; set; } override protected void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("myConnectionString"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Product>() .ToTable("Product") .HasKey(_ => _.Id); modelBuilder.Entity<Product>() .Property(_ => _.Id) .HasColumnName("ProductId"); modelBuilder.Entity<Product>() .HasOne(_ => _.SizeUnitMeasure) .WithMany() .HasForeignKey(_ => _.SizeUnitMeasureCode); modelBuilder.Entity<Product>() .HasOne(_ => _.WeightUnitMeasure) .WithMany() .HasForeignKey(_ => _.WeightUnitMeasureCode); modelBuilder.Entity<UnitMeasure>() .ToTable("UnitMeasure") .HasKey(_ => _.UnitMeasureCode); modelBuilder.Entity<ProductModel>() .ToTable("ProductModel") .HasKey(_ => _.Id); modelBuilder.Entity<ProductModel>() .Property(_ => _.Id) .HasColumnName("ProductModelID"); } }
实体类代码:
public class Product { public int Id { get; private set; } public Guid ObjectId { get; private set; } public string? Name { get; private set; } public ProductModel? ProductModel { get; private set; } public string? SizeUnitMeasureCode { get; private set; } public UnitMeasure? SizeUnitMeasure { get; set; } public string? WeightUnitMeasureCode { get; private set; } public UnitMeasure? WeightUnitMeasure { get; set; } } public class ProductModel { public int Id { get; private set; } public Guid ObjectId { get; private set; } public string? Name { get; private set; } } public class UnitMeasure { public string? UnitMeasureCode { get; private set; } public Guid ObjectId { get; private set; } public string? Name { get; private set; } }
问题现象:Product实体的SizeUnitMeasure和WeightUnitMeasure导航属性查询后未被填充,但ProductModel属性正常填充。EF生成的SQL查询语句看似正确,关联查询也返回了正确结果:
SELECT [p].[ProductId], [p].[Name], [p].[ObjectId], [p].[ProductModelId], [p].[SizeUnitMeasureCode], [p].[WeightUnitMeasureCode], [p0].[ProductModelID], [p0].[Name], [p0].[ObjectId], [u].[UnitMeasureCode], [u].[Name], [u].[ObjectId], [u0].[UnitMeasureCode], [u0].[Name], [u0].[ObjectId] FROM [Product] AS [p] LEFT JOIN [ProductModel] AS [p0] ON [p].[ProductModelId] = [p0].[ProductModelID] LEFT JOIN [UnitMeasure] AS [u] ON [p].[SizeUnitMeasureCode] = [u].[UnitMeasureCode] LEFT JOIN [UnitMeasure] AS [u0] ON [p].[WeightUnitMeasureCode] = [u0].[UnitMeasureCode] WHERE [p].[ObjectId] = 'df86dd7f-727d-462f-93e8-6a9caf577a42'
已尝试的操作:修改Product类中UnitMeasure属性的setter为公共无效;将SizeUnitMeasureCode列重命名为SizeUnitMeasureUnitMeasureCode并移除HasForeignKey配置,问题依旧。
问题原因分析
UnitMeasure主键的私有setter限制
UnitMeasure的主键UnitMeasureCode使用私有setter,EF Core映射查询结果时,需要正确设置主键值来识别实体并关联导航属性。虽然ProductModel主键也是私有,但它是数值类型(int),EF可通过默认构造逻辑初始化;而字符串类型的主键私有setter会导致EF无法正确赋值,进而无法将查询到的UnitMeasure实例关联到Product的对应导航属性。同类型多导航属性的映射歧义
同时Include两个指向UnitMeasure的导航属性时,EF需要区分两个不同实例。如果主键无法正确设置,EF无法识别这两个实例的差异,导致无法匹配到对应的导航属性。
解决方案
方案1:将UnitMeasure主键的setter改为公共
修改UnitMeasure类的主键属性,开放setter:
public class UnitMeasure { public string? UnitMeasureCode { get; set; } public Guid ObjectId { get; private set; } public string? Name { get; private set; } }
这样EF可以正常设置主键值,完成实体关联。
方案2:为UnitMeasure配置可被EF识别的私有构造函数
若要保持setter私有,可添加私有无参构造函数,并在模型配置中声明:
public class UnitMeasure { private UnitMeasure() { } public string? UnitMeasureCode { get; private set; } public Guid ObjectId { get; private set; } public string? Name { get; private set; } }
在OnModelCreating中添加配置:
modelBuilder.Entity<UnitMeasure>() .ToTable("UnitMeasure") .HasKey(_ => _.UnitMeasureCode) .HasConstructor();
该方式让EF可以通过反射调用私有构造函数初始化实体。
方案3:验证数据库关联值的有效性
手动执行生成的SQL,确认返回的u.UnitMeasureCode和u0.UnitMeasureCode不为null,且与UnitMeasure表中的UnitMeasureCode完全匹配(注意字符串大小写、空格等细节)。
内容的提问来源于stack exchange,提问作者ngruson

