如何使用一个linq查询获得总数量?

时间:2013-12-28 13:09:28

标签: c# linq

我有两个linq查询,一个获取confirmedQty,另一个获取unconfirmedQty

获得unconfirmedQty的条件。它应该是平均而不是总和。

result = Sum(confirmedQty) + Avg(unconfirmedQty)

有没有办法只编写一个查询并获得所需的结果,而不是编写两个单独的查询?

我的代码

class Program
{
    static void Main(string[] args)
    {
        List<Item> items = new List<Item>(new Item[]
        {
            new Item{ Qty = 100, IsConfirmed=true },
            new Item{ Qty = 40, IsConfirmed=false },
            new Item{ Qty = 40, IsConfirmed=false },
            new Item{ Qty = 40, IsConfirmed=false },
        });

        int confirmedQty = Convert.ToInt32(items.Where(o => o.IsConfirmed == true).Sum(u => u.Qty));
        int unconfirmedQty = Convert.ToInt32(items.Where(o => o.IsConfirmed != true).Average(u => u.Qty));

        //Output => Total : 140
        Console.WriteLine("Total : " + (confirmedQty + unconfirmedQty));

        Console.Read();
    }

    public class Item
    {
        public int Qty { get; set; }
        public bool IsConfirmed { get; set; }
    }
}

1 个答案:

答案 0 :(得分:3)

实际接受的答案会列举您的项目集2N + 1次,这会给原始解决方案带来不必要的复杂性。如果我遇到这段代码

(from t in items
 let confirmedQty = items.Where(o => o.IsConfirmed == true).Sum(u => u.Qty)
 let unconfirmedQty = items.Where(o => o.IsConfirmed != true).Average(u => u.Qty)
 let total = confirmedQty + unconfirmedQty
 select new { tl = total }).FirstOrDefault();

需要一些时间来了解您投影项目的数据类型。是的,这个查询是一个奇怪的投影。它创建SelectIterator来投射每个序列项,然后创建一些范围变量,包括迭代项两次,最后选择第一个投影项。基本上,您已将原始查询包装到其他无用的查询中:

items.Select(i => {
    var confirmedQty = items.Where(o => o.IsConfirmed).Sum(u => u.Qty);
    var unconfirmedQty = items.Where(o => !o.IsConfirmed).Average(u => u.Qty);
    var total = confirmedQty + unconfirmedQty;
    return new { tl = total };
}).FirstOrDefault();

Intent在代码中隐藏,您仍然拥有相同的两个嵌套查询。你能在这做什么?您可以简化两个查询,使其更易于可读并清楚地显示您的意图

int confirmedTotal = items.Where(i => i.IsConfirmed).Sum(i => i.Qty);
// NOTE: Average will throw exception if there is no unconfirmed items!
double unconfirmedAverage = items.Where(i => !i.IsConfirmed).Average(i => i.Qty);
int total = confirmedTotal + (int)unconfirmedAverage;

如果性能比可读性更重要,那么您可以在单个查询中计算总计(为了便于阅读,转移到扩展方法):

public static int Total(this IEnumerable<Item> items)
{
    int confirmedTotal = 0;
    int unconfirmedTotal = 0;
    int unconfirmedCount = 0;

    foreach (var item in items)
    {
        if (item.IsConfirmed)
        {
            confirmedTotal += item.Qty;
        }
        else
        {
            unconfirmedCount++;
            unconfirmedTotal += item.Qty;
        }
    }

    if (unconfirmedCount == 0)
        return confirmedTotal;
    // NOTE: Will not throw if there is no unconfirmed items
    return confirmedTotal + unconfirmedTotal / unconfirmedCount;
}

用法很简单:

items.Total();

BTW接受答案的第二个解决方案不正确。它只是一个巧合,它返回正确的值,因为你有所有未经证实的项目具有相同的Qty。此解决方案计算总和而不是平均值。分组解决方案如下:

var total = 
    items.GroupBy(i => i.IsConfirmed)
         .Select(g => g.Key ? g.Sum(i => i.Qty) : (int)g.Average(i => i.Qty))
         .Sum();

在这里,您将项目分组为两组 - 已确认和未确认。然后根据组密钥计算总和或平均值,并计算两个组值的摘要。这既不是可读也不是有效的解决方案,但它是正确的。