【问题标题】:Get sum of two columns in one LINQ query在一个 LINQ 查询中获取两列的总和
【发布时间】:2011-01-26 19:10:41
【问题描述】:

假设我有一张名为 项目(ID int、Done int、Total int)

我可以通过两个查询来做到这一点:

int total = m.Items.Sum(p=>p.Total)
int done = m.Items.Sum(p=>p.Done)

但我想在一个查询中完成,如下所示:

var x = from p in m.Items select new { Sum(p.Total), Sum(p.Done)};

肯定有一种方法可以从 LINQ 语法中调用聚合函数...?

【问题讨论】:

    标签: c# .net linq linq-to-sql sum


    【解决方案1】:

    使用C# 7.0 中引入的元组语言支持,您可以使用以下 LINQ 表达式解决此问题:

    var itemSums = m.Items.Aggregate((Total: 0, Done: 0), (sums, item) => (sums.Total + item.Total, sums.Done + item.Done));
    

    完整代码示例:

    var m = new
    {
        Items = new[]
        {
            new { Total = 10, Done = 1 },
            new { Total = 10, Done = 1 },
            new { Total = 10, Done = 1 },
            new { Total = 10, Done = 1 },
            new { Total = 10, Done = 1 },
        },
    };
    
    var itemSums = m.Items.Aggregate((Total: 0, Done: 0), (sums, item) => (sums.Total + item.Total, sums.Done + item.Done));
    
    Console.WriteLine($"Sum of Total: {itemSums.Total}, Sum of Done: {itemSums.Done}");
    

    【讨论】:

      【解决方案2】:

      这已经得到回答,但其他答案仍会在集合上进行多次迭代(多次调用 Sum)或创建许多中间对象/元组,这可能很好,但如果不是,那么你可以创建一个(或多个)扩展方法,以老式方式执行此操作,但非常适合 LINQ 表达式。

      这样的扩展方法如下所示:

      public static Tuple<int, int> Sum<T>(this IEnumerable<T> collection, Func<T, int> selector1, Func<T, int> selector2)
      {
          int a = 0;
          int b = 0;
      
          foreach(var i in collection)
          {
              a += selector1(i);
              b += selector2(i);
          }
      
          return Tuple.Create(a, b);
      }
      

      你可以这样使用它:

      public class Stuff
      {
          public int X;
          public int Y;
      }
      
      //...
      
      var stuffs = new List<Stuff>()
      {
          new Stuff { X = 1, Y = 10 }, 
          new Stuff { X = 1, Y = 10 }
      };
      
      var sums = stuffs.Sum(s => s.X, s => s.Y);
      

      【讨论】:

        【解决方案3】:

        这样就可以了:

        from p in m.Items
        group p by 1 into g
        select new
        {
            SumTotal = g.Sum(x => x.Total), 
            SumDone = g.Sum(x => x.Done) 
        };
        

        【讨论】:

        • 或者当Item没有唯一标识符时,你可以写group p by p into g
        • 这成功了,尽管做了一些修改:从 p in m.Items group p by p.Id into g select new { SumTotal = g.Sum(r => r.Total), SumDone = g.Sum(r => r.Done) }
        • @vcRobe:您真的遇到给定方法不合适的问题吗?我认为使用 LINQ 没有简单的方法可以做到这一点。您可能必须遍历项目并将临时结果存储在 Dictionary.
        • @Steven,是的,我有这种情况,这就是我发现这个问题的原因,但我需要避免重复收集。我在想如何解决这两个问题,我明白了。我写了另一个替代你的答案。
        • 这个答案不正确 - 如果您按项目上的唯一标识符分组,那么您将只对单个项目求和。然后结果将是可枚举的匿名类型,其元素数与 Items 中的行数一样多。 @WhoIsNinja 是对的 - 您需要使用常量进行分组。
        【解决方案4】:

        要对表格求和,请按常数分组:

        from p in m.Items
        group p by 1 into g
        select new {
            SumTotal = g.Sum(x => x.Total),
            SumDone = g.Sum(x => x.Done)
        }
        

        【讨论】:

        • 请问有人能发个vb.net版本吗?
        • @Zeus 这是 VB.Net 代码,从 g In From p In m.ItemsGroup p By 1New With { _ Key .SumTotal = g.Sum(Function(x) x.Total), _ Key .SumDone = g.Sum(Function(x) x.Done) _ } 你可以在这里转换代码link
        【解决方案5】:

        当您使用 group by 时,Linq 会创建一个新的项目集合,因此您有两个项目集合。

        以下是解决这两个问题的方法:

        1. 在一次迭代中对任意数量的成员求和并
        2. 避免重复您的项目集合

        代码:

        public static class LinqExtensions
        {
          /// <summary>
          /// Computes the sum of the sequence of System.Double values that are obtained 
          /// by invoking one or more transform functions on each element of the input sequence.
          /// </summary>
          /// <param name="source">A sequence of values that are used to calculate a sum.</param>
          /// <param name="selectors">The transform functions to apply to each element.</param>    
          public static double[] SumMany<TSource>(this IEnumerable<TSource> source, params Func<TSource, double>[] selectors)
          {
            if (selectors.Length == 0)
            {
              return null;
            }
            else
            {
              double[] result = new double[selectors.Length];
        
              foreach (var item in source)
              {
                for (int i = 0; i < selectors.Length; i++)
                {
                  result[i] += selectors[i](item);
                }
              }
        
              return result;
            }
          }
        
          /// <summary>
          /// Computes the sum of the sequence of System.Decimal values that are obtained 
          /// by invoking one or more transform functions on each element of the input sequence.
          /// </summary>
          /// <param name="source">A sequence of values that are used to calculate a sum.</param>
          /// <param name="selectors">The transform functions to apply to each element.</param>
          public static double?[] SumMany<TSource>(this IEnumerable<TSource> source, params Func<TSource, double?>[] selectors) 
          { 
            if (selectors.Length == 0)
            {
              return null;
            }
            else
            {
              double?[] result = new double?[selectors.Length];
        
              for (int i = 0; i < selectors.Length; i++)
              {
                result[i] = 0;
              }
        
              foreach (var item in source)
              {
                for (int i = 0; i < selectors.Length; i++)
                {
                  double? value = selectors[i](item);
        
                  if (value != null)
                  {
                    result[i] += value;
                  }
                }
              }
        
              return result;
            }
          }
        }
        

        这是你必须进行求和的方式:

        double[] result = m.Items.SumMany(p => p.Total, q => q.Done);
        

        这是一个通用示例:

        struct MyStruct
        {
          public double x;
          public double y;
        }
        
        MyStruct[] ms = new MyStruct[2];
        
        ms[0] = new MyStruct() { x = 3, y = 5 };
        ms[1] = new MyStruct() { x = 4, y = 6 };
        
        // sum both x and y members in one iteration without duplicating the array "ms" by GROUPing it
        double[] result = ms.SumMany(a => a.x, b => b.y);
        

        如你所见

        result[0] = 7 
        result[1] = 11
        

        【讨论】:

          【解决方案6】:
          //Calculate the total in list field values
          //Use the header file: 
          
          Using System.Linq;
          int i = Total.Sum(G => G.First);
          
          //By using LINQ to calculate the total in a list field,
          
          var T = (from t in Total group t by Total into g select g.Sum(t => t.First)).ToList();
          
          //Here Total is a List and First is the one of the integer field in list(Total)
          

          【讨论】:

            【解决方案7】:

            弄清楚在我的其余代码中从何处提取总和或其他聚合使我感到困惑,直到我记得我构造的变量是一个 Iqueryable。假设我们的数据库中有一个由 Orders 组成的表,我们想为 ABC 公司生成一个摘要:

            var myResult = from g in dbcontext.Ordertable
                           group p by (p.CUSTNAME == "ABC") into q  // i.e., all of ABC company at once
                           select new
            {
                tempPrice = q.Sum( x => (x.PRICE ?? 0m) ),  // (?? makes sure we don't get back a nullable)
                tempQty = q.Sum( x => (x.QTY ?? 0m) )
            };
            

            现在有趣的部分—— tempPrice 和 tempQty 没有在任何地方声明,但它们必须是 myResult 的一部分,不是吗?按如下方式访问它们:

            Console.Writeline(string.Format("You ordered {0} for a total price of {1:C}",
                                             myResult.Single().tempQty,
                                             myResult.Single().tempPrice ));
            

            也可以使用许多其他 Queryable 方法。

            【讨论】:

              【解决方案8】:

              怎么样

                 m.Items.Select(item => new { Total = item.Total, Done = item.Done })
                        .Aggregate((t1, t2) => new { Total = t1.Total + t2.Total, Done = t1.Done + t2.Done });
              

              【讨论】:

              • 我认为这对 SQL 不友好。
              【解决方案9】:

              使用辅助元组类,无论是您自己的还是(在 .NET 4 中)标准的,您都可以这样做:

              var init = Tuple.Create(0, 0);
              
              var res = m.Items.Aggregate(init, (t,v) => Tuple.Create(t.Item1 + v.Total, t.Item2 + v.Done));
              

              res.Item1Total 列和Done 列的res.Item2 的总和。

              【讨论】:

                猜你喜欢
                • 2012-05-01
                • 1970-01-01
                • 2018-07-16
                • 2021-11-16
                • 2017-07-14
                • 1970-01-01
                • 1970-01-01
                • 2021-01-26
                • 1970-01-01
                相关资源
                最近更新 更多