【发布时间】:2021-04-23 22:02:19
【问题描述】:
上下文
我有一个使用 Razor Pages 构建的简单链接聚合器 ASP.NET Core 应用程序。
每个链接的详细信息页面显示了 cmets:
用户可以评论链接并回复 cmets。
型号
这是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剃刀页面
这是Details 页面的OnGetAsync 方法。
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 的各种导航属性。
当用户向Link 添加评论时,Comment 的LinkId 具有值。当用户向Comment 添加评论时,Comment 的ParentCommentId 具有值。
由于cmets可以有cmets,所以我使用递归内部函数load_comments通过explicit loading检索cmets树。
建议的方法
通过 Google 搜索 ef core include recursive 可以找到各种加载递归实体的方法。
-
stackoverflow 上的This answer 推荐使用递归函数方法,有点类似于我上面展示的。
-
但是,有经验的用户this popular answer 建议可以通过
Include(eager loading) 检索整个树,而无需使用显式加载。
问题
有没有办法使用急切加载而不是显式加载来获取 Link 的 cmets?看起来这将是一个比显式递归方法更简单的实现。
上面的第二个答案表明它应该可以工作。但是,在加载 cmets 页面时,实施该建议(及其一些变体)会导致运行时错误。
完整项目
完整的项目可在 github 上找到:LinkAggregatorComments。这只是项目的快照,用于此类问题。
上面讨论的一些部分的链接:
- Link
- Comment
- DetailsModel
- Details剃须刀页面
【问题讨论】:
-
您所指的答案中的关键点是whole这个词,即没有任何过滤的完整表格。由于您要按
Link过滤,因此不适用。这意味着您又回到了 EF Core 中缺乏分层 (CTE) 支持的状态。顺便说一句,如果您为子 cmets 存储了LinkId- 是的,我知道它是多余的并且来自父节点,但它会给您一个简单的非分层过滤条件,用于所有链接 cmets,因此使用简单的 @987654356 的技术@ 会起作用的。 -
感谢您的 cmets @IvanStoev。我已经在gist 中发布了我的回复,因为这里的 cmets 太长了。只是想确保我理解您的建议。
-
是的,你没看错。确实,通过这种方法,
Link.Comments将包含Link的所有 cmets - 类似于包含所有 cmets 的DbSet<Comment>,而不仅仅是顶部的 cmets。但是您总是可以在需要时使用Link.Comments.Where(c => c,ParentCommentId == null)轻松过滤掉排名靠前的那些。并确保在保存现有Link时不要删除Comments集合中缺少的 cmets。该解决方案有其缺点 - 虽然它解决了负载问题,但它使其他一些操作变得复杂,包括序列化/反序列化...... -
...所以这取决于您对涉及的实体的所有 CRUD 操作有多少控制权。
-
@IvanStoev,感谢您的确认。我明白你的意思,这两种方法各有利弊。
标签: c# entity-framework asp.net-core razor entity-framework-core