【问题标题】:Implementing recursive property loading in EF Core在 EF Core 中实现递归属性加载
【发布时间】:2016-12-06 03:32:08
【问题描述】:

我使用的是 .NET Core 1.1.0、EF Core 1.1.0、VS 2015。

我正在为帖子/cmets 编写一个系统,我需要一个函数来加载评论及其所有子项及其相关属性。这是我的课程的简化版本:

public class Comment
{
    public long Id { get; set; }

    public string Content { get; set; }

    public User User { get; set; }

    public ICollection<Comment> Replies { get; set; }
}

public class User
{
    public long Id { get; set; }

    public string Name { get; set; }

    public Avatar Avatar { get; set; }
}

public class Avatar
{
    public string Url { get; set; }
}

任何给定的评论都可以有任意数量的回复:

-PARENT
    -CHILD 1
    -CHILD 2
        -CHILD 3
    -CHILD 4
    -CHILD 5
        -CHILD 6
            -CHILD 7

因此,给定父评论的 ID,我需要加载整个树,包括用户及其各自的头像。 (我在其他地方进行了控制,以确保这些树不会变得笨拙,此时我并不担心可能会获取太多数据。)

EF Core 文档中的Loading Related Data 页面非常有用,但我不确定如何最好地处理这个问题。我已经尝试将一些东西放在一起,但我无法概念化如何将它们组合在一起。再次注意:我使用的是 EF Core 1.1.0,因此我可以访问“显式加载”部分中的函数。

如何在给定父评论 ID 的情况下加载整个 cmets 树?

【问题讨论】:

  • 每个孩子到数据库的往返行程可能非常重要。使用 SQL 进行递归 CTE 并让 EF 映射结果可能会更好。
  • @ESG 我以前从未听说过 CTE,在阅读了它们之后,它看起来可能是完美的。我再仔细看看,谢谢!

标签: c# entity-framework asp.net-core entity-framework-core


【解决方案1】:

我没有数据库,所以我只是在内存中完成了它,但如果你关注我的 cmets,它会为你工作。注意我在内存中的对象,只有 id 为 2 的评论有回复。

LoadComment 方法是一切发生的地方。剩下的只是我需要的设置代码。

class Program
{
    static void Main(string[] args)
    {
        var result = LoadComment(1, null);
        Console.ReadKey();

    }



public static Comment LoadComment(long id, Comment com) 
{
   Comment res = new Comment();
   if( com == null ) 
   {
      // You would call your context here and write db.Single(x => x.Id == id).Include(x => x.User.Avatar);
      var first = db.Single( x => x.Id == id );

      res = new Comment { Id = first.Id, Replies = first.Replies.ToList(), User = first.User };
      foreach( var item in first.Replies ) 
      {
         LoadComment( item.Id, item );
      }
   }
   else 
   {
      // You would call your context here and write db.Single(x => x.Id == id).Include(x => x.User.Avatar);
      var child = db.SingleOrDefault( x => x.Id == id );
      if( child == null ) 
      {
         return null;
      }
      com.Replies = new List<Comment>();
      com.Replies.Add( new Comment { Id = child.Id, Replies = child.Replies.ToList(), User = child.User } );
      foreach( var item in child.Replies ) 
      {
         LoadComment( item.Id, com );
      }
   }


   return res;
}

    private static Comment cm1 = new Comment
    {
        Id = 1,
        User = new User { Id = 1, Avatar = new Avatar { Url = "1" } },
        Replies = new List<Comment> {
        new Comment { Id = 2 },
        new Comment { Id = 3 },
        new Comment { Id = 4 },
        new Comment { Id = 5 } },
        Content = "ContentForCommentId1"
    };

    private static Comment cm2 = new Comment
    {
        Id = 2,
        User = new User { Id = 2, Avatar = new Avatar { Url = "2" } },
        Replies = new List<Comment> {
        new Comment { Id = 22 },
        new Comment { Id = 33 },
        new Comment { Id = 44 },
        new Comment { Id = 55 } },
        Content = "ContentForCommentId2"
    };
    private static List<Comment> db = new List<Comment> { cm1, cm2 };

}

【讨论】:

  • 这看起来很有希望!我在最后一个 foreach: Collection was modified; enumeration operation may not execute. 收到了这个错误,它发生在加载第一个评论的孩子之后; foreach 中的第二项触发异常。
  • 抱歉,if 和 else 块中有一个小错误。请使用LoadComment 方法中的新代码重试。如果它不起作用,那么我将不得不创建一个数据库并尝试以这种方式对其进行测试。
  • 我最终使用了这种方法,只是稍微调整了一下。您提供的更新代码完美运行,我只是为我的代码库更改了几处。非常感谢!
【解决方案2】:

我就是这样解决的。与 Yoshi 的非常相似,但可能对某人有所帮助。

private async Task<Comment> GetComment(Guid id, CancellationToken cancellationToken)
{
    var wm = await _context.Comments
                           .Include(x => x.Replies)
                           .SingleOrDefaultAsync(x => x.Id == id, cancellationToken);

    for (var i = 0; i < wm.Replies.Count; i++)
    {
        if (!wm.Replies[i].IsDeleted)
            wm.Replies[i] = await GetComment(wm.Replies[i].Id, cancellationToken);
    }

    return wm;
}

【讨论】:

    【解决方案3】:

    为什么不使用 Eagerly Loading? 急切地加载多个级别 我在 .NETCore (VS2015) 的项目中使用过它

    也可以急切地加载多个级别的相关实体。下面的查询显示了如何对集合和参考导航属性执行此操作的示例。

    using (var context = new BloggingContext()) 
    { 
        // Load all blogs, all related posts, and all related comments 
        var blogs1 = context.Blogs 
                           .Include(b => b.Posts.Select(p => p.Comments)) 
                           .ToList(); 
    
        // Load all users their related profiles, and related avatar 
        var users1 = context.Users 
                            .Include(u => u.Profile.Avatar) 
                            .ToList(); 
    
        // Load all blogs, all related posts, and all related comments  
        // using a string to specify the relationships 
        var blogs2 = context.Blogs 
                           .Include("Posts.Comments") 
                           .ToList(); 
    
        // Load all users their related profiles, and related avatar  
        // using a string to specify the relationships 
        var users2 = context.Users 
                            .Include("Profile.Avatar") 
                            .ToList(); 
    }
    

    请注意,目前无法过滤加载了哪些相关实体。包含将始终引入所有相关实体。

    这是我的参考:"Microsoft.EntityFrameworkCore.SqlServer": "1.1.0-preview2-22683"

    希望对你有帮助。

    【讨论】:

    • 据我了解,我必须为我想要引入的每一层链接.Include().ThenInclude(),这是不可行的,因为事先不知道 cmets 的深度。这不正确吗?
    • 嗯..你是对的。你当然应该知道孩子和孙子。对于多层,.ThenInclude() 也可能存在一些问题。见:stackoverflow.com/questions/40953665/…
    猜你喜欢
    • 2022-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    相关资源
    最近更新 更多