使用LINQ进行多个SUM运算

11

我有一个类似下面的循环,我能否使用多个SUM实现相同的功能?

foreach (var detail in ArticleLedgerEntries.Where(pd => pd.LedgerEntryType == LedgerEntryTypeTypes.Unload &&
                                                                pd.InventoryType == InventoryTypes.Finished))
{
     weight += detail.GrossWeight;
     length += detail.Length;
     items  += detail.NrDistaff;
}

1
LINQ并不是数据操作的全部和终极解决方案,使用for循环也没有任何问题。 - SLaks
有趣的是,@SLaks,这是LINQ无法提供合理解决方案的罕见情况之一。 - PeterX
5个回答

8

从技术角度来看,您目前拥有的可能是实现您要求的最有效方式。但是,您可以在IEnumerable<T>上创建一个名为Each的扩展方法,这可能会使它更简单:

public static class EnumerableExtensions
{
    public static void Each<T>(this IEnumerable<T> col, Action<T> itemWorker)
    {
        foreach (var item in col)
        {
            itemWorker(item);
        }
    }
}

然后像这样调用:

// Declare variables in parent scope
double weight;
double length;
int items;

ArticleLedgerEntries
    .Where(
        pd => 
           pd.LedgerEntryType == LedgerEntryTypeTypes.Unload &&
           pd.InventoryType == InventoryTypes.Finished
    )
    .Each(
        pd => 
        {
            // Close around variables defined in parent scope
            weight += pd.GrossWeight; 
            lenght += pd.Length;
            items += pd.NrDistaff;
        }
    );

更新: 仅有一个额外的注意事项。上面的示例依赖于闭包。变量weight,length和items应该在父作用域中声明,以允许它们在每次调用itemWorker操作后持久存在。我已经更新了示例以反映这一点,以增强其清晰度。


2
+1,好答案。请注意,MoreLinq已经为IEnumerable<T>声明了ForEach扩展方法。另请注意,Eric Lippert 不喜欢这个想法 - Ilya Ivanov

5

你可以调用Sum三次,但这样会变慢,因为它会进行三次循环。

例如:

var list = ArticleLedgerEntries.Where(pd => pd.LedgerEntryType == LedgerEntryTypeTypes.Unload
                                   && pd.InventoryType == InventoryTypes.Finished))

var totalWeight = list.Sum(pd => pd.GrossWeight);
var totalLength = list.Sum(pd => pd.Length);
var items = list.Sum(pd => pd.NrDistaff); 

由于延迟执行,每次都会重新评估Where调用,尽管在您的情况下这并不是什么问题。通过调用ToArray可以避免这种情况,但这将导致数组分配。(而且它仍然会运行三个循环)
然而,除非您有大量条目或在紧密循环中运行此代码,否则无需担心性能。
编辑:如果您真的想使用LINQ,可以误用Aggregate,如下所示:
int totalWeight, totalLength, items;

list.Aggregate((a, b) => { 
    weight += detail.GrossWeight;
    length += detail.Length;
    items  += detail.NrDistaff;
    return a;
});

这是极其丑陋的代码,但是执行效率几乎和直接循环一样好。

你也可以在累加器中进行求和(见下面的示例),但这会为列表中的每个项分配一个临时对象,这是一个愚蠢的想法。(匿名类型是不可变的)

var totals = list.Aggregate(
    new { Weight = 0, Length = 0, Items = 0},
    (t, pd) => new { 
        Weight = t.Weight + pd.GrossWeight,
        Length = t.Length + pd.Length,
        Items = t.Items + pd.NrDistaff
    }
);

好的。我意识到使用LINQ没有简单的方法来做到这一点。我会采用我的foreach循环,因为我明白它并不那么糟糕。谢谢大家。 - Alessandro
能否请您对用户805138的回答发表评论?他的方法表现如何? - Andrzej Gis
@gisek:group x by 1 完全没有用,非常愚蠢;它只是为了引入LINQ语法而已。除此之外,它与我的第一段代码完全相同;只是多使用了两个循环。 - SLaks
LINQ to Entities是否支持这个? - Jayantha Lal Sirisena
@Jayantha:我猜你在问“Aggregate”误用的问题。第一个肯定不是(块lambda不能成为表达式树);第二个几乎肯定也不是。 - SLaks

2
你也可以按true - 1分组(实际上包括任何项,然后对它们进行计数或求和):
 var results = from x in ArticleLedgerEntries
                       group x by 1
                       into aggregatedTable
                       select new
                                  {
                                      SumOfWeight = aggregatedTable.Sum(y => y.weight),
                                      SumOfLength = aggregatedTable.Sum(y => y.Length),
                                      SumOfNrDistaff = aggregatedTable.Sum(y => y.NrDistaff)
                                  };

就运行时间而言,它几乎和循环一样好(加上一个常数)。


group by 完全没有用,而且相当令人困惑。只需执行 var results = new { ... = ArticleLedgerEntries.Sum(...), ... } 即可。 - SLaks

0

0

好的,我意识到使用LINQ没有简单的方法来做到这一点。我会采用我的foreach循环,因为我明白它并不那么糟糕。感谢大家。


你不应该像在论坛主题上回复一样在 Stack Overflow 上发布答案。只有在回答自己的问题时才这样做。通常,您需要在原始问题中添加“更新”以对回答和评论进行回复。 - Mike de Klerk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接