首页 > 解决方案 > 使用 foreach 循环加速迭代 List<>,每次迭代都涉及数据库操作

问题描述

在我正在工作的应用程序中,每个用户都有一个提交。PeerGroup对于每次提交,我需要根据给定的大小 ( )创建一组用户 ( groupSize)。例如,对于每个提交,PeerGroup可以创建 3 个学生中的一个。的大小submissions可以扩展到 1000。

我有以下内容可以遍历submissions列表。在每个循环中,我根据组成员的数量按升序对用户进行排序 ( PeerGroupMemberships)。如果一个用户被分配了更多的提交,那么他们应该在底部,并且不应该被选中。然后我Take用来留住那些用户。通过这种方式,我试图保持平衡。

 List<Submission> submissions = _context.Submissions.Where(s => s.ReviewRoundId == reviewRoundId).ToList();

 foreach (Submission submission in submissions)
 {
     if (submission.PeerGroup == null)
     {
         PeerGroup peerGroup = new PeerGroup { SubmissionId = submission.Id};
         _context.PeerGroups.Add(peerGroup);
         _context.SaveChanges();

         IEnumerable<ApplicationUserDto> peers =
             _context.ApplicationUsers
                     .Where(s => s.Submissions.Select(ce => ce.ReviewRoundId).Contains(reviewRoundId))
                     .Where(s => s.Id != submission.StudentId)   
                     .OrderBy(m => m.PeerGroupMemberships.Count(pg => pg.PeerGroup.Submission.ReviewRoundId == reviewRoundId))
                     .Select(m => new ApplicationUserDto
                     {
                         FullName = m.FullName,
                         Id = new Guid(m.Id),
                         ProfilePhoto = m.ProfilePhoto,
                         NumberOfPeersToReview = m.PeerGroupMemberships.Count(pg => pg.PeerGroup.Submission.ReviewRoundId == reviewRoun
                     }).Take(groupSize);


         foreach (ApplicationUserDto p in peers)
         {
             PeerGroupMembership groupMembership = new PeerGroupMembership { UserId = p.Id.ToString(), PeerGroupId = peerGroup.Id  };
             _context.PeerGroupMemberships.Add(groupMembership);
             _context.SaveChanges();
         }
     }
 }

代码运行良好,但每个循环需要将近 5 秒,这可能会导致很长的延迟,需要循环 1000 次提交。

我想知道这是否正常,或者是否可以以某种方式改进代码。有什么建议么?

标签: c#entity-frameworkloopsforeach

解决方案


有几件事:首先,您似乎没有利用 EF 来映射实体之间的关系。对于一般的批量更新方案,通过将 FK 分配给实体并保存通常更有效,但对于插入,当您依赖数据库分配需要检索以设置 FK 的 PK 时,成本会更高。

至少因为您要插入一个 PeerGroup 和一个 PeerGroupMembership,所以您应该映射这两个实体之间的关系,以便您可以创建 PeerGroup,然后将其关联到新的成员资格,并允许 EF 在您执行单个操作时计算出 FK SaveChanges 调用。

例如,您的代码可以大大加快速度,如下所示:

var submissions = _context.Submissions
    .Where(s => s.ReviewRoundId == reviewRoundId && s.PeerGroup == null)
    .Select(s => {Submissionid = s.Id, s.SudentId).ToList();

foreach(var submission in submissions)
{
    var peerGroup = new PeerGroup{ SubmissionId = s.SubmissionId };
    _context.PeerGroups.Add(peerGroup);

    var unassignedUserIds = _context.ApplicationUsers
        .Where(u => u.Submissions.Any(s => s.ReviewRoundId == reviewRoundId
            && u.Id != submission.StudentId)
        .OrderBy(u => u.PeerGroupMemberships.Count(pg => pg.PeerGroup.Submission.ReviewRoundId == reviewRoundId))
        .Select(u => Id)
        .Take(groupSize);

    foreach(var userId in unassignedUserIds)
    {
        var groupMembership = new PeerGroupMembership { UserId = userId.ToString(), PeerGroup = peerGroup };
        _context.PeerGroupMemberships.Add(groupMemberShip);
    }
}
_context.SaveChanges();

这里的重点:选择提交时,您只需要提交ID和学生ID。我们还可以消除所有已经有对等组的提交。不需要拉其余的。在拉取 Peers 时,我们可以使用.AnyReviewRound 来查找 Peers,我们只需要选择 User ID。通过在 PeerGroup 和 PeerGroupMembership 之间映射关系,我们可以创建 PeerGroupMembership 实体并将其 PeerGroup 分配给我们在上面创建的新实体。当SaveChanges()被调用时,它将插入一组实体,确保首先插入 PeerGroups,并且在保存成员资格时正确映射 FK。附带说明,为什么 PeerGroupMembership 中的 UserId 是字符串?这看起来可能是返回给 ApplicationUser 的 FK,因此它应该与数据类型匹配。

为了调整性能,目标是只加载您需要的数据,而不是其他任何东西。这适用于读取的列和读取的行数。


推荐阅读