EF Core自引用列表的配置

5

我在尝试配置存在自引用的实体时遇到了一些问题。我的实体看起来大致如下:

public class MainEntity 
{
  public Guid Id {get; private set;}
  ...
  public IReadOnlyList<Foo> Foos => _foos;
  private readonly List<Foo> _foos {get; private set;} = new List<Foo>();
}
public class Foo
{
  public Guid Id {get; private set;}
  public Guid MainEntityId {get; private set;}

  public IReadOnlyList<Bar> Bars => _bars;
  private readonly List<Bar> _bars {get; private set;} = new List<Bar>();
  
}
public class Bar
{
  public Guid Id {get; private set;}
  public Guid? FooId {get; private set;}
  public Guid? ParentBarId {get; private set;}

  public IReadOnlyList<Bar> Bars => _bars;
  private readonly List<Bar> _bars {get; private set;} = new List<Bar>();  
}

基本上,MainEntity 包含了一个 Foos 列表,该列表包含一个 Bars 的列表,其中可能带有内部的 Bars 列表(您可以将 MainEntity 视为某个用户配置文件;Fools 为用户帖子列表;Bars 为某个帖子的评论或一些父级评论的回复)。

最初我认为一切都很好,但后来发现通过执行 context.MainEntities.Include(x => x.Foos).ThenInclude(x => x.Bars).First(); ,我只得到了第一层 Bars(具有 FooId),而且某个 Bar 的 Bars 始终为空。

我可以多次使用 ThenInclude(x => x.Bars),但这并不是真正意义上的解决方法,因为最大深度没有限制,会影响检索性能。

我尝试将 Bar.FooId 设置为非空,并进行填充,但没有成功(context.SaveChangesAsync() 无法解决关系,可能是 ModelBuilder 中的配置错误)。另外,我不确定是否会导致其他问题,例如 Foo 包含所有具有 FooIdBars,即使 Bar 具有 ParentBar 也是如此...

有时我的解释可能会令人困惑,如果需要任何澄清,请告诉我。


据我所知,您无法使用贪婪加载来实现此目的。您必须手动指定查询的深度,以便知道要加入到表中多少次,或者使用延迟加载来自动执行每个遍历深度的N+1查询。 - Kieran Devlin
脑海中浮现的一个解决方法可能是在Foo中添加另一个guid,该guid将传播到所有随后的Bars。因此,两个数据库查询就可以解决问题:context.MainEntities.Include(x => x.Foos).First() 然后 context.Bars.Where(x => mainEntity.Foos.Select(xx => xx.Guid).Contains(x.Guid)).ToList() 但我认为这是一种hack方法,应该手动传递Guids。 - Kilas
你会遇到的问题是序列化程序不知道记录所在的深度,因此你将拥有所有记录,但只有一个深度层。我假设你想保留对象嵌套,但如果你只使用ID就可以解决问题,那么这也是一个好的解决方案。 - Kieran Devlin
因此,附加的Guid属性将没有引用,序列化程序应该通过FooId和BarId值来确定深度。据我所知,它应该可以工作,但我想避免这种hack。 - Kilas
1
你应该将FooId设置为非空,然后就可以查找任何foo的所有bar了。如果出现错误,那就是另一个问题,但如果正确设置了每个Bar的FooId,就不应该有错误了。然后,在选择所需的所有Foos和每个Foo的所有bars之后,通过一个简单的函数,你可以嵌套地组织所有bars。 - mostafa khoramnia
显示剩余2条评论
1个回答

0

所以,解决方案非常简单。首先,我将演示我如何修改实体:

public class MainEntity
{
   public Guid Id { get; set; }

   public string Name { get; set; }

   public IReadOnlyList<Foo> Foos { get; set; }
}

public class Foo
{
   public Guid Id { get; set; }

   public string Name { get; set; }

   public Guid MainEntityId { get; set; }

   public IReadOnlyList<Bar> Bars { get; set; }
}

public class Bar
{
   public Guid Id { get; set; }

   public string Name { get; set; }

   public Guid? FooId { get; set; }

   public Guid? ParentBarId { get; set; }

   public virtual Bar ParentBar { get; set; } 
   public virtual ICollection<Bar> ChildrenBars { get; set; }
}

正如您所看到的,我还添加了一个类型为BarParentBar字段。

下一步是在您的AppDbContext类中覆盖OnModelCreating方法,如下所示:

...

public DbSet<MainEntity> MainEntities { get; set; }

public DbSet<Foo> Foos { get; set; }

public DbSet<Bar> Bars { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<Bar>()
           .HasMany(b => b.ChildrenBars)
           .WithOne(b=>b.ParentBar)
           .HasForeignKey(b=>b.ParentBarId);    
}

最后,在您的控制器中,您可以像下面这样调用MainEntites

return await _context.MainEntities.Include(me=>me.Foos)
            .ThenInclude(me=>me.Bars).ToListAsync();

This会返回以下所示的响应:

api response

我希望我的回答对你有所帮助!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接