首页 > 解决方案 > 加载相关数据——递归注释

问题描述

语境

我有一个使用 Razor Pages 构建的简单链接聚合器 ASP.NET Core 应用程序。

在此处输入图像描述

每个链接的详细信息页面显示评论:

用户可以评论链接并回复评论。

楷模

这是Link课程:

    public class Link
    {
        public int Id { get; set; }
        public string UserId { get; set; }

        [DataType(DataType.Url)]
        public string Url { get; set; }

        public string Title { get; set; }

        [Display(Name = "Date")]
        [DataType(DataType.Date)]
        public DateTime DateTime { get; set; }

        [ForeignKey("UserId")]
        public IdentityUser User { get; set; }

        public List<Vote> Votes { get; set; }

        public int Score() => Votes.Sum(vote => vote.Score);

        public Vote UserVote(string userId) => 
            Votes.FirstOrDefault(vote => vote.UserId == userId);

        public int UserScore(string userId)
        {
            var vote = UserVote(userId);

            return vote == null ? 0 : vote.Score;
        }

        public async Task Vote(int score, string voterUserId)
        {
            var vote = UserVote(voterUserId);

            if (vote == null)
            {
                vote = new Vote()
                {
                    UserId = voterUserId,
                    LinkId = Id,
                    Score = score,
                    DateTime = DateTime.Now
                };

                Votes.Add(vote);
            }
            else
            {
                vote.Score = vote.Score == score ? 0 : score;
            }
        }
              
        public List<Comment> Comments { get; set; }

        public async Task AddComment(string text, string commenterUserId)
        {
            var comment = new Comment()
            {
                UserId = commenterUserId,
                LinkId = Id,
                Text = text,
                DateTime = DateTime.Now
            };

            Comments.Add(comment);
        }
    }

这是Comment课程:

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

        public int? LinkId { get; set; }
        //[ForeignKey("LinkId")]
        public Link Link { get; set; }

        public int? ParentCommentId { get; set; }
        //[ForeignKey("ParentCommentId")]
        public Comment ParentComment { get; set; }

        public List<Comment> Comments { get; set; }

        public string Text { get; set; }
        public DateTime DateTime { get; set; }

        public string UserId { get; set; }
        [ForeignKey("UserId")]
        public IdentityUser User { get; set; }

        public List<CommentVote> Votes { get; set; }

        public int Score() =>
            Votes
                .Where(vote => vote.CommentId == Id)
                .Sum(vote => vote.Score);

        public CommentVote UserVote(string userId) =>
            Votes.FirstOrDefault(vote => vote.UserId == userId);

        public int UserScore(string userId)
        {
            var vote = UserVote(userId);

            return vote == null ? 0 : vote.Score;
        }

        public async Task Vote(int score, string voterUserId)
        {
            var vote = UserVote(voterUserId);

            if (vote == null)
            {
                vote = new CommentVote()
                {
                    UserId = voterUserId,
                    CommentId = Id,
                    Score = score,
                    DateTime = DateTime.Now
                };

                Votes.Add(vote);
            }
            else
            {
                vote.Score = vote.Score == score ? 0 : score;
            }
        }

        public async Task AddComment(string text, string commenterUserId)
        {
            var comment = new Comment()
            { 
                UserId = commenterUserId,
                ParentCommentId = Id,
                Text = text,
                DateTime = DateTime.Now
            };

            Comments.Add(comment);
        }
    }

Details剃刀页面

这是页面的OnGetAsync方法Details

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Link = await _context.Link
        .Include(link => link.User)
        .Include(link => link.Votes)
        .Include(link => link.Comments)
        .FirstOrDefaultAsync(m => m.Id == id);

    void load_comments(List<Comment> comments)
    {
        foreach (var comment in comments)
        {
            _context.Entry(comment).Reference(comment => comment.User).Load();
            
            _context.Entry(comment).Collection(comment => comment.Comments).Load();

            _context.Entry(comment).Collection(comment => comment.Votes).Load();

            load_comments(comment.Comments);
        }
    }

    load_comments(Link.Comments);

    if (Link == null)
    {
        return NotFound();
    }
    return Page();
}

如您所见,我Include使用Link.

当用户向 a 添加评论时LinkLinkIdComment有一个值。当用户向 a 添加评论时CommentParentCommentIdComment有一个值。

由于评论可以有评论,我使用递归内部函数load_comments通过显式加载检索评论树。

建议的方法

Google 搜索ef core 包括递归导致加载递归实体的各种方法。

问题

有没有办法获取Link使用急切加载而不是显式加载的评论?与显式递归方法相比,这似乎是一个更简单的实现。

上面的第二个答案表明它应该起作用。但是,在加载评论页面时,实施该建议(及其一些变体)会导致运行时错误。

完整项目

完整的项目可在 github 上找到:LinkAggregatorComments。这只是项目的快照,用于此类问题。

链接到上面讨论的一些部分:

标签: c#entity-frameworkasp.net-corerazorentity-framework-core

解决方案


如果所有子评论都引用了该链接,那么您根本不需要该load_comment部分。EF 在将父子关系加载到上下文时修复它们,并Include(link => link.Comments)加载所有它们。

加载评论的投票和用户ThenInclude


推荐阅读