2009-08-27 3 views
8

이전 SQL을 LINQ to SQL로 다시 작성하려고합니다. 나는 루블과 함께 그룹을 가진 sproc을 가지고있다. 그러나 LINQ와 동등한 것이 무엇인지 모르겠다. LINQ는 GroupBy를 가지고 있지만 ROLLUP을 지원하지는 않습니다.GROUP BY WITH ROLLUP의 LINQ to SQL 버전

나는 이런 식으로 뭔가 될 것 얻기 위해 노력하고있어 결과의 간단한 예 :

 
+-----------+---------------+--------------------+ 
| City | ServicePlan | NumberOfCustomers | 
+-----------+---------------+--------------------+ 
| Seattle | Plan A  |     10 | 
| Seattle | Plan B  |     5 | 
| Seattle | All   |     15 | 
| Portland | Plan A  |     20 | 
| Portland | Plan C  |     10 | 
| Portland | All   |     30 | 
| All  | All   |     45 | 
+-----------+---------------+--------------------+ 

내가 SQL로 LINQ를 사용하여 이러한 결과를 얻을 수있는 방법에 대한 아이디어를?

답변

10

나는 훨씬 더 간단한 해결책을 알아 냈어. 나는 그것이 필요한 것보다 더 복잡하게하려고 노력했다. 3-5 개의 클래스/메소드가 필요하기보다는 하나의 메소드 만 필요합니다.

기본적으로 정렬 및 그룹화를 수행 한 다음 WithRollup()으로 전화하면 소계와 총합이있는 항목 중 List<>이 표시됩니다. SQL 측에서 소계와 총계를 생성하는 방법을 알아낼 수 없으므로 LINQ to Objects로 처리됩니다.

/// <summary> 
/// Adds sub-totals to a list of items, along with a grand total for the whole list. 
/// </summary> 
/// <param name="elements">Group and/or sort this yourself before calling WithRollup.</param> 
/// <param name="primaryKeyOfElement">Given a TElement, return the property that you want sub-totals for.</param> 
/// <param name="calculateSubTotalElement">Given a group of elements, return a TElement that represents the sub-total.</param> 
/// <param name="grandTotalElement">A TElement that represents the grand total.</param> 
public static List<TElement> WithRollup<TElement, TKey>(this IEnumerable<TElement> elements, 
    Func<TElement, TKey> primaryKeyOfElement, 
    Func<IGrouping<TKey, TElement>, TElement> calculateSubTotalElement, 
    TElement grandTotalElement) 
{ 
    // Create a new list the items, subtotals, and the grand total. 
    List<TElement> results = new List<TElement>(); 
    var lookup = elements.ToLookup(primaryKeyOfElement); 
    foreach (var group in lookup) 
    { 
     // Add items in the current group 
     results.AddRange(group); 
     // Add subTotal for current group 
     results.Add(calculateSubTotalElement(group)); 
    } 
    // Add grand total 
    results.Add(grandTotalElement); 

    return results; 
} 

을 그리고 그것을 사용하는 방법의 예 : 여기 코드는

class Program 
{ 
    static void Main(string[] args) 
    { 
     IQueryable<CustomObject> 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(); 

     IQueryable<CustomObject> orderedElements = from item in dataItems 
                orderby item.City, item.Plan 
                group item by new { item.City, item.Plan } into grouping 
                select new CustomObject 
                { 
                 City = grouping.Key.City, 
                 Plan = grouping.Key.Plan, 
                 Charges = grouping.Sum(item => item.Charges), 
                 Count = grouping.Count() 
                }; 

     List<CustomObject> results = orderedElements.WithRollup(
      item => item.City, 
      group => new CustomObject 
      { 
       City = group.Key, 
       Plan = "All", 
       Charges = group.Sum(item => item.Charges), 
       Count = group.Sum(item => item.Count) 
      }, 
      new CustomObject 
      { 
       City = "All", 
       Plan = "All", 
       Charges = orderedElements.Sum(item => item.Charges), 
       Count = orderedElements.Sum(item => item.Count) 
      }); 

     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); 
    } 
} 
4

알았습니다. 일반 GroupByWithRollup. 두 개의 열로만 그룹화되지만 더 쉽게 확장 할 수 있습니다. 아마도 세 개의 열을 허용하는 다른 버전이있을 것입니다. 주요 클래스/메소드는 Grouping <>, GroupByMany <>() 및 GroupByWithRollup <>()입니다. GroupByWithRollup <>()을 실제로 사용할 때 SubTotal() 및 GrandTotal() 메서드는 도우미입니다. 아래 코드는 사용법 예를 보여줍니다.

/// <summary> 
/// Represents an instance of an IGrouping<>. Used by GroupByMany(), GroupByWithRollup(), and GrandTotal(). 
/// </summary> 
public class Grouping<TKey, TElement> : IGrouping<TKey, TElement> 
{ 
    public TKey Key { get; set; } 
    public IEnumerable<TElement> Items { get; set; } 

    public IEnumerator<TElement> GetEnumerator() 
    { 
     return Items.GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return Items.GetEnumerator(); 
    } 
} 

public static class Extensions 
{ 
    /// <summary> 
    /// Groups by two columns. 
    /// </summary> 
    /// <typeparam name="TElement">Type of elements to group.</typeparam> 
    /// <typeparam name="TKey1">Type of the first expression to group by.</typeparam> 
    /// <typeparam name="TKey2">Type of the second expression to group by.</typeparam> 
    /// <param name="orderedElements">Elements to group.</param> 
    /// <param name="groupByKey1Expression">The first expression to group by.</param> 
    /// <param name="groupByKey2Expression">The second expression to group by.</param> 
    /// <param name="newElementExpression">An expression that returns a new TElement.</param> 
    public static IQueryable<Grouping<TKey1, TElement>> GroupByMany<TElement, TKey1, TKey2>(this IOrderedQueryable<TElement> orderedElements, 
     Func<TElement, TKey1> groupByKey1Expression, 
     Func<TElement, TKey2> groupByKey2Expression, 
     Func<IGrouping<TKey1, TElement>, IGrouping<TKey2, TElement>, TElement> newElementExpression 
     ) 
    { 
     // Group the items by Key1 and Key2 
     return from element in orderedElements 
       group element by groupByKey1Expression(element) into groupByKey1 
       select new Grouping<TKey1, TElement> 
       { 
        Key = groupByKey1.Key, 
        Items = from key1Item in groupByKey1 
          group key1Item by groupByKey2Expression(key1Item) into groupByKey2 
          select newElementExpression(groupByKey1, groupByKey2) 
       }; 
    } 

    /// <summary> 
    /// Returns a List of TElement containing all elements of orderedElements as well as subTotals and a grand total. 
    /// </summary> 
    /// <typeparam name="TElement">Type of elements to group.</typeparam> 
    /// <typeparam name="TKey1">Type of the first expression to group by.</typeparam> 
    /// <typeparam name="TKey2">Type of the second expression to group by.</typeparam> 
    /// <param name="orderedElements">Elements to group.</param> 
    /// <param name="groupByKey1Expression">The first expression to group by.</param> 
    /// <param name="groupByKey2Expression">The second expression to group by.</param> 
    /// <param name="newElementExpression">An expression that returns a new TElement.</param> 
    /// <param name="subTotalExpression">An expression that returns a new TElement that represents a subTotal.</param> 
    /// <param name="totalExpression">An expression that returns a new TElement that represents a grand total.</param> 
    public static List<TElement> GroupByWithRollup<TElement, TKey1, TKey2>(this IOrderedQueryable<TElement> orderedElements, 
     Func<TElement, TKey1> groupByKey1Expression, 
     Func<TElement, TKey2> groupByKey2Expression, 
     Func<IGrouping<TKey1, TElement>, IGrouping<TKey2, TElement>, TElement> newElementExpression, 
     Func<IGrouping<TKey1, TElement>, TElement> subTotalExpression, 
     Func<IQueryable<Grouping<TKey1, TElement>>, TElement> totalExpression 
     ) 
    { 
     // Group the items by Key1 and Key2 
     IQueryable<Grouping<TKey1, TElement>> groupedItems = orderedElements.GroupByMany(groupByKey1Expression, groupByKey2Expression, newElementExpression); 

     // Create a new list the items, subtotals, and the grand total. 
     List<TElement> results = new List<TElement>(); 
     foreach (Grouping<TKey1, TElement> 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; 
    } 

    /// <summary> 
    /// Returns the subTotal sum of sumExpression. 
    /// </summary> 
    /// <param name="sumExpression">An expression that returns the value to sum.</param> 
    public static int SubTotal<TKey, TElement>(this IGrouping<TKey, TElement> query, Func<TElement, int> sumExpression) 
    { 
     return query.Sum(group => sumExpression(group)); 
    } 

    /// <summary> 
    /// Returns the subTotal sum of sumExpression. 
    /// </summary> 
    /// <param name="sumExpression">An expression that returns the value to sum.</param> 
    public static decimal SubTotal<TKey, TElement>(this IGrouping<TKey, TElement> query, Func<TElement, decimal> sumExpression) 
    { 
     return query.Sum(group => sumExpression(group)); 
    } 

    /// <summary> 
    /// Returns the grand total sum of sumExpression. 
    /// </summary> 
    /// <param name="sumExpression">An expression that returns the value to sum.</param> 
    public static int GrandTotal<TKey, TElement>(this IQueryable<Grouping<TKey, TElement>> query, Func<TElement, int> sumExpression) 
    { 
     return query.Sum(group => group.Sum(innerGroup => sumExpression(innerGroup))); 
    } 

    /// <summary> 
    /// Returns the grand total sum of sumExpression. 
    /// </summary> 
    /// <param name="sumExpression">An expression that returns the value to sum.</param> 
    public static decimal GrandTotal<TKey, TElement>(this IQueryable<Grouping<TKey, TElement>> query, Func<TElement, decimal> sumExpression) 
    { 
     return query.Sum(group => group.Sum(innerGroup => sumExpression(innerGroup))); 
    } 

그리고 그것을 사용하는 예

는 :

class Program 
{ 
    static void Main(string[] args) 
    { 
     IQueryable<CustomObject> 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<CustomObject> 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); 
    } 
} 
+0

바바라, 아직 거기에 버그가 있습니다. 실제로 Func <> 대신 Expression >을 사용해야하기 때문에 실제 SQL 데이터에 대해 실행할 때 예외가 발생합니다. 또한 식에서 "from x in y"구문을 사용할 수 없습니다. 이 도움말은 http://www.richardbushnell.net/index.php/2008/01/16/using-lambda-expressions-with-linq-to-sql/에서 도움이되었습니다. 그래서 나는 그것을 깨끗하게 할 필요가있다. – Ecyrb

+0

이 방법은 필요한 것보다 훨씬 더 복잡합니다. SQL 측에서 전적으로 그룹화 작업을 할 수 없었습니다. 결국, 나는이 접근법을 포기하고 훨씬 더 간단하게 수용된 해결책을 제시했다. – Ecyrb

2

@Ecyrb을, 안녕하세요로부터 5 년 후!

저는 표준 LINQ (개체에 대한 것) 이상의 LINQ to SQL을 모호하게 알고 있습니다. 그러나 "LINQ-2-SQL"태그와 별도로 "LINQ"태그가 있기 때문에 (데이터베이스에 변경 사항을 등록하는 것과는 대조적으로) 주로 결과에 관심이있는 것처럼 보이기 때문에 SQL Server의 "롤업"그룹화 기능에 해당하는 LINQ 검색을 검색 할 때 필자가 본 실제 관련 리소스는 비슷한 요구 사항을 가진 다른 모든 사용자에게 내 자신의 대체 솔루션을 제공 할 것입니다.

기본적으로 내 접근 방식은 ".OrderBy(). ThenBy()"구문과 유사한 ".GroupBy(). ThenBy()"체인 가능 구문을 만드는 것입니다. 내 확장 기능은 ".GroupBy()"를 소스로 사용하여 얻은 결과 인 IGrouping 객체의 컬렉션을 기대합니다. 그런 다음 컬렉션을 가져 와서 그룹을 해제하여 그룹화하기 전에 원래 개체로 되돌아갑니다. 마지막으로 새 그룹화 함수에 따라 데이터를 다시 그룹화하고 다른 그룹의 IGrouping 개체를 생성하며 새로 그룹화 된 개체를 원본 개체 집합에 추가합니다.

public static class mySampleExtensions { 

    public static IEnumerable<IGrouping<TKey, TSource>> ThenBy<TSource, TKey> (  
     this IEnumerable<IGrouping<TKey, TSource>> source, 
     Func<TSource, TKey> keySelector) { 

     var unGroup = source.SelectMany(sm=> sm).Distinct(); // thank you flq at http://stackoverflow.com/questions/462879/convert-listlistt-into-listt-in-c-sharp 
     var reGroup = unGroup.GroupBy(keySelector); 

     return source.Concat(reGroup);} 

} 

당신은 ".ThenBy()"기능의 적절한 영역에 상수 값을 넣어 SQL 서버의 롤업 논리에 맞게 방법을 사용할 수 있습니다. 캐스팅에 가장 유연한 상수이기 때문에 null 값을 사용하는 것을 선호합니다. .GroupBy()와 .ThenBy() 둘 다에서 사용하는 함수가 동일한 객체 유형을 가져야하므로 Casting이 중요합니다.당신이 8 월 (31) '09에 대한 첫 번째 응답에서 만든 "dataItems"변수를 사용하여, 그 결과는 다음과 같습니다

var rollItUp = dataItems 
    .GroupBy(g=> new {g.City, g.Plan}) 
     .ThenBy(g=> new {g.City, Plan = (string) null}) 
     .ThenBy(g=> new {City = (string) null, Plan = (string) null}) 
    .Select(s=> new CustomObject { 
     City = s.Key.City, 
     Plan = s.Key.Plan, 
     Count = s.Count(), 
     Charges = s.Sum(a=> a.Charges)}) 
    .OrderBy(o=> o.City) // This line optional 
     .ThenBy(o=> o.Plan); // This line optional 

당신은 "모든"과 ".ThenBy()"논리에 널 (null)을 대체 할 수있는, 네가 원하는대로

잠재적으로 SQL Server의 그룹화 집합을 에뮬레이션 할 수 있으며 ".ThenBy()"를 사용하여 큐브를 만들 수 있습니다. 또한 ".ThenBy()"는 저에게 잘 작동합니다. ".OrderBy()"메소드의 ".ThenBy()"와 동일한 이름의 문제는 다른 서명이 없기 때문에 보지 않습니다. 문제가 발생하면 ".ThenGroupBy()"라는 이름을 사용하여 구별하는 것이 좋습니다.

필자는 Linq-to-SQL을 사용하지 않지만 여러 측면에서 Linq-to-SQL을 사용하는 F # 유형 공급자 시스템을 사용합니다. 그래서 내 F # 프로젝트에서 그런 개체에 내 확장을 시도하고 예상대로 작동합니다. 이 점에있어서 흥미롭지 않거나 의미가없는 것이 전혀없는 것은 아닙니다.