当它们相互交叉引用时,我如何在实体框架中加载嵌套实体?



这个有点复杂,所以我创建了一个示例项目(说明在自述文件中)

https://github.com/dominicshaw/EntityFrameworkNestingQuirk

我有以下代码:

private async Task<(bool Found, Appraisal Appraisal)> Get(int id)
{
var staff = await _context.Staff.FindAsync(3);
_logger.LogInformation("Got Staff Object   : Staff {id} has manager set to {managerId} - running query for appraisal", staff.Id, staff.ManagerId);
var appraisal =
await _context.Appraisals
.Include(ap => ap.Staff).ThenInclude(s => s.Manager)
.Include(ap => ap.Staff).ThenInclude(s => s.SecondaryManager)
.Where(ap => ap.Id == id)
.SingleOrDefaultAsync();
_logger.LogInformation("Appraisal Query Run: Staff {id} has manager set to {managerId} - run completed", staff.Id, staff.ManagerId);
if (appraisal != null)
{
_logger.LogInformation("Appraisal->Staff->2ndManager->Manager={id} (We should NOT have this)", appraisal.Staff.SecondaryManager?.ManagerId);
return (true, appraisal);
}
return (false, null);
}

数据永远不会写入,staff id 3有两个管理器,都设置在第一个日志行。

然后我从数据库中获取该员工的评估- ef生成的查询看起来不错,并返回了两个经理,但是在运行该查询时,我丢失了员工对象(3)上的经理id(应该是1)。

当第二个日志被命中时,managerid为空。

我注意到EF模型已经加载了管理器的二级管理器,即使我没有要求它(除非我的EF查询语法错误?)。经理(1)是2和3的经理,所以这是正确的,但它应该被加载到Appraisal->Staff-> manager中,而不是Appraisal->Staff->SecondaryManager-> manager

我该如何解决这个问题?

问题是EF Core认为ManagerSecondaryManager的自引用关系是一对一的关系,这进一步意味着它认为它只能将给定的实体分配给单个导航属性。

下面的OnModelCreating()配置在本地为我解决了这个问题。与您提供的配置相比,只有注释行被更改:

builder.Entity<Staff>().HasIndex(e => e.ManagerId).IsUnique(false);
builder.Entity<Staff>()
.HasOne(a => a.Manager)
.WithMany() // Changed
.HasForeignKey(s => s.ManagerId)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);

builder.Entity<Staff>().HasIndex(e => e.SecondaryManagerId).IsUnique(false);
builder.Entity<Staff>()
.HasOne(a => a.SecondaryManager)
.WithMany() // Changed
.HasForeignKey(s => s.SecondaryManagerId)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);

为什么嵌套的Manager被填充?

原因是EF Core的"关系修复"行为,这意味着如果你加载一个被导航属性引用的实体,它会自动将该实体分配给给定的导航属性。

即使你运行两个完全独立的查询,如果在单独加载的实体之间存在关系连接,EF Core也会在内存中将它们连接在一起。

在本例中,主Manager和嵌套Manager都引用Id为1的相同Staff实体,因此它将在内存中"修复"两者。但是,它恰好先"修复"嵌套的那个,鉴于一对一的约束,我想它就停在那里了。也许有更深入了解EF Core内部工作原理的人会给我们更多的细节。

最新更新