LINQ to SQL version of GROUP BY WITH ROLLUP

后端 未结 4 529
予麋鹿
予麋鹿 2020-12-31 17:56

I\'m trying to rewrite some old SQL into LINQ to SQL. I have a sproc with a GROUP BY WITH ROLLUP but I\'m not sure what the LINQ equivalent would be. LINQ has a GroupBy bu

4条回答
  •  温柔的废话
    2020-12-31 18:49

    I got it! A generic GroupByWithRollup. It only groups by two columns, but could easily be extended to support more. I'll probably have another version that accepts three columns. The key classes/methods are Grouping<>, GroupByMany<>(), and GroupByWithRollup<>(). The SubTotal() and GrandTotal() methods are helpers when you actually use GroupByWithRollup<>(). Below is the code, followed by an example of how to use it.

    /// 
    /// Represents an instance of an IGrouping<>.  Used by GroupByMany(), GroupByWithRollup(), and GrandTotal().
    /// 
    public class Grouping : IGrouping
    {
        public TKey Key { get; set; }
        public IEnumerable Items { get; set; }
    
        public IEnumerator GetEnumerator()
        {
            return Items.GetEnumerator();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return Items.GetEnumerator();
        }
    }
    
    public static class Extensions
    {
        /// 
        /// Groups by two columns.
        /// 
        /// Type of elements to group.
        /// Type of the first expression to group by.
        /// Type of the second expression to group by.
        /// Elements to group.
        /// The first expression to group by.
        /// The second expression to group by.
        /// An expression that returns a new TElement.
        public static IQueryable> GroupByMany(this IOrderedQueryable orderedElements,
            Func groupByKey1Expression,
            Func groupByKey2Expression,
            Func, IGrouping, TElement> newElementExpression
            )
        {
            // Group the items by Key1 and Key2
            return from element in orderedElements
                   group element by groupByKey1Expression(element) into groupByKey1
                   select new Grouping
                   {
                       Key = groupByKey1.Key,
                       Items = from key1Item in groupByKey1
                               group key1Item by groupByKey2Expression(key1Item) into groupByKey2
                               select newElementExpression(groupByKey1, groupByKey2)
                   };
        }
    
        /// 
        /// Returns a List of TElement containing all elements of orderedElements as well as subTotals and a grand total.
        /// 
        /// Type of elements to group.
        /// Type of the first expression to group by.
        /// Type of the second expression to group by.
        /// Elements to group.
        /// The first expression to group by.
        /// The second expression to group by.
        /// An expression that returns a new TElement.
        /// An expression that returns a new TElement that represents a subTotal.
        /// An expression that returns a new TElement that represents a grand total.
        public static List GroupByWithRollup(this IOrderedQueryable orderedElements,
            Func groupByKey1Expression,
            Func groupByKey2Expression,
            Func, IGrouping, TElement> newElementExpression,
            Func, TElement> subTotalExpression,
            Func>, TElement> totalExpression
            )
        {
            // Group the items by Key1 and Key2
            IQueryable> groupedItems = orderedElements.GroupByMany(groupByKey1Expression, groupByKey2Expression, newElementExpression);
    
            // Create a new list the items, subtotals, and the grand total.
            List results = new List();
            foreach (Grouping item in groupedItems)
            {
                // Add items under current group
                results.AddRange(item);
                // Add subTotal for current group
                results.Add(subTotalExpression(item));
            }
            // Add grand total
            results.Add(totalExpression(groupedItems));
    
            return results;
        }
    
        /// 
        /// Returns the subTotal sum of sumExpression.
        /// 
        /// An expression that returns the value to sum.
        public static int SubTotal(this IGrouping query, Func sumExpression)
        {
            return query.Sum(group => sumExpression(group));
        }
    
        /// 
        /// Returns the subTotal sum of sumExpression.
        /// 
        /// An expression that returns the value to sum.
        public static decimal SubTotal(this IGrouping query, Func sumExpression)
        {
            return query.Sum(group => sumExpression(group));
        }
    
        /// 
        /// Returns the grand total sum of sumExpression.
        /// 
        /// An expression that returns the value to sum.
        public static int GrandTotal(this IQueryable> query, Func sumExpression)
        {
            return query.Sum(group => group.Sum(innerGroup => sumExpression(innerGroup)));
        }
    
        /// 
        /// Returns the grand total sum of sumExpression.
        /// 
        /// An expression that returns the value to sum.
        public static decimal GrandTotal(this IQueryable> query, Func sumExpression)
        {
            return query.Sum(group => group.Sum(innerGroup => sumExpression(innerGroup)));
        }
    

    And an example of using it:

    class Program
    {
        static void Main(string[] args)
        {
            IQueryable dataItems = (new[]
            {
                new CustomObject { City = "Seattle", Plan = "Plan B", Charges = 20 },
                new CustomObject { City = "Seattle", Plan = "Plan A", Charges = 10 },
                new CustomObject { City = "Seattle", Plan = "Plan B", Charges = 20 },
                new CustomObject { City = "Seattle", Plan = "Plan A", Charges = 10 },
                new CustomObject { City = "Seattle", Plan = "Plan A", Charges = 10 },
                new CustomObject { City = "Seattle", Plan = "Plan A", Charges = 10 },
                new CustomObject { City = "Portland", Plan = "Plan A", Charges = 10 },
                new CustomObject { City = "Portland", Plan = "Plan A", Charges = 10 },
                new CustomObject { City = "Portland", Plan = "Plan C", Charges = 30 },
                new CustomObject { City = "Portland", Plan = "Plan C", Charges = 30 },
                new CustomObject { City = "Portland", Plan = "Plan C", Charges = 30 }
            }).AsQueryable();
    
            List results = dataItems.OrderBy(item => item.City).ThenBy(item => item.Plan).GroupByWithRollup(
                item => item.City,
                item => item.Plan,
                (primaryGrouping, secondaryGrouping) => new CustomObject
                {
                    City = primaryGrouping.Key,
                    Plan = secondaryGrouping.Key,
                    Count = secondaryGrouping.Count(),
                    Charges = secondaryGrouping.Sum(item => item.Charges)
                },
                item => new CustomObject
                {
                    City = item.Key,
                    Plan = "All",
                    Count = item.SubTotal(subItem => subItem.Count),
                    Charges = item.SubTotal(subItem => subItem.Charges)
                },
                items => new CustomObject
                {
                    City = "All",
                    Plan = "All",
                    Count = items.GrandTotal(subItem => subItem.Count),
                    Charges = items.GrandTotal(subItem => subItem.Charges)
                }
                );
            foreach (var result in results)
                Console.WriteLine(result);
    
            Console.Read();
        }
    }
    
    class CustomObject
    {
        public string City { get; set; }
        public string Plan { get; set; }
        public int Count { get; set; }
        public decimal Charges { get; set; }
    
        public override string ToString()
        {
            return String.Format("{0} - {1} ({2} - {3})", City, Plan, Count, Charges);
        }
    }
    

提交回复
热议问题