c# - 使用 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 次提交。
我想知道这是否正常,或者是否可以以某种方式改进代码。有什么建议么?
解决方案
有几件事:首先,您似乎没有利用 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 时,我们可以使用.Any
ReviewRound 来查找 Peers,我们只需要选择 User ID。通过在 PeerGroup 和 PeerGroupMembership 之间映射关系,我们可以创建 PeerGroupMembership 实体并将其 PeerGroup 分配给我们在上面创建的新实体。当SaveChanges()
被调用时,它将插入一组实体,确保首先插入 PeerGroups,并且在保存成员资格时正确映射 FK。附带说明,为什么 PeerGroupMembership 中的 UserId 是字符串?这看起来可能是返回给 ApplicationUser 的 FK,因此它应该与数据类型匹配。
为了调整性能,目标是只加载您需要的数据,而不是其他任何东西。这适用于读取的列和读取的行数。
推荐阅读
- django - Django 重定向到另一个应用程序中的不同视图
- hadoop - 从 hbase 复制数据时运行 map reduce 作业时找不到 Jar 文件异常
- fabricjs - 如何在 Fabric JS 上更改矩形形状
- html - 哪部分代码控制子菜单悬停状态
- jenkins - Jenkins 管道语法 - 如果安装了某些东西
- python - 向图表添加数字
- linux - 不同版本的 cmake 可以产生不同的结果吗?
- docker - 有什么作用。是指码头工人?它是指图像的当前工作目录还是本地机器?
- javascript - 按评级过滤下拉菜单
- ios - 在 Firebase 中使用自定义对象数组存储自定义对象:Swift