将嵌套的foreach写为 LINQ 以聚合两个单独的值

本文关键字:单独 两个 foreach 嵌套 写为 LINQ | 更新日期: 2023-09-27 18:36:46

我正在尝试汇总用户在单个挑战中可以在游戏中获得的积分和硬币总数。玩家可以在挑战中获得硬币和积分的层次结构如下:

  • 挑战
      • 步进块
一个挑战可以有多个步骤

,一个步骤可以有多个步骤块。

已将其编写为嵌套的foreach,但这会导致对数据库进行大量查询,我想知道这是否可以通过在数据库上聚合的LINQ查询来解决,该查询仅返回一个具有两个值的对象,一个用于总可实现点数,一个用于可实现的总硬币。

代码如下:

foreach (var challenge in userChallenges)
        {
            var coins = 0;
            var points = 0;
            coins += challenge.CoinsWhenCompleted;
            points += challenge.PointsWhenCompleted;
            var steps = await db.Steps.Where(s => s.ChallengeId == challenge.ChallengeId).ToListAsync().ConfigureAwait(false);
            foreach (var step in steps)
            {
                coins += step.CoinsWhenCompleted;
                points += step.PointsWhenCompleted;
                var blocks = await db.StepBlocks.Where(b => b.StepId == step.StepId).ToListAsync().ConfigureAwait(false);
                foreach (var block in blocks)
                {
                    coins += block.CoinsWhenCompleted;
                    points += block.PointsWhenCompleted;
                }
            }
            challenge.TotalPossibleCoins = coins;
            challenge.TotalPossiblePoints = points;
        }

CoinsWhenCompletedPointsWhenCompleted是该特定挑战、步骤或步骤块的最高分数。

我尝试环顾四周,但找不到何时有多个值要聚合。

任何帮助不胜感激,谢谢!

将嵌套的foreach写为 LINQ 以聚合两个单独的值

你在这里的主要问题是你有两个选择 n + 1 的问题。

您可以将其重写为以下内容:

var challengeIDs = userChallenges.Select(c => c.ChallengeId).Distinct();
var steps = await db.Steps.Where(s => challengeIDs.Contains(s.ChallengeId))
    .ToListAsync()
    .ConfigureAwait(false);
var stepIDs = steps.Select(s => s.StepId).Distinct();
var blocks = await db.StepBlocks.Where(b => stepIDs.Contains(b.StepId))
    .ToListAsync()
    .ConfigureAwait(false);
foreach (var challenge in userChallenges)
{
    challenge.TotalPossibleCoins = challenge.CoinsWhenCompleted;
    challenge.TotalPossiblePoints = challenge.PointsWhenCompleted;
    var challengeSteps = steps.Where(s => s.ChallengeId == challenge.ChallengeId).ToList();
    var stepBlocks = stepBlocks.Where(b => challengeSteps
        .Any(c => c.StepId == b.StepId))
        .ToList();
    challenge.TotalPossibleCoins += challengeSteps.Sum(c => c.CoinsWhenCompleted);
    challenge.TotalPossiblePoints += challengeSteps.Sum(c => c.PointsWhenCompleted);
    challenge.TotalPossibleCoins += stepBlocks.Sum(c => c.CoinsWhenCompleted);
    challenge.TotalPossiblePoints += stepBlocks.Sum(c => c.PointsWhenCompleted);
}

解释:

此查询

var steps = await db.Steps.Where(s => s.ChallengeId == challenge.ChallengeId).ToListAsync().ConfigureAwait(false);

和这个查询

var blocks = await db.StepBlocks.Where(b => b.StepId == step.StepId).ToListAsync().ConfigureAwait(false);

正在为循环的每次迭代执行

为了防止这种情况,我们可以简单地执行上述两个查询,但在foreach循环之前,通过执行以下操作:

var challengeIDs = userChallenges.Select(c => c.ChallengeId).Distinct();
var steps = await db.Steps.Where(s => challengeIDs.Contains(s.ChallengeId))
    .ToListAsync()
    .ConfigureAwait(false);
var stepIDs = steps.Select(s => s.StepId).Distinct();
var blocks = await db.StepBlocks.Where(b => stepIDs.Contains(b.StepId))
    .ToListAsync()
    .ConfigureAwait(false);

这意味着无论我们有多少步骤或用户挑战,我们只执行两个查询。

此外,我们只需在 Linq 中使用 Sum 即可重构foreach语句,以获取每个子集合的聚合总计。