我正在尝试将一些旧的SQL重写为LINQ to SQL。我有一个带有GROUP BY WITH ROLLUP的sproc但是我不确定LINQ等价物是什么。 LINQ有一个GroupBy,但看起来它不支持ROLLUP。
我试图获得的结果的简化示例将是这样的:
+-----------+---------------+--------------------+ | 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 | +-----------+---------------+--------------------+
关于如何使用LINQ to SQL获取这些结果的任何想法?
答案 0 :(得分:11)
我想出了一个更简单的解决方案。我试图让它变得比它需要的更复杂。而不是需要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);
}
}
答案 1 :(得分:4)
我明白了!通用GroupByWithRollup。它只按两列分组,但可以轻松扩展以支持更多。我可能会有另一个版本接受三列。关键类/方法是Grouping&lt;&gt ;,GroupByMany&lt;&gt;()和GroupByWithRollup&lt;&gt;()。实际使用GroupByWithRollup&lt;&gt;()时,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);
}
}
答案 2 :(得分:2)
@Ecyrb,五年后你好!
我只是模糊地熟悉LINQ to SQL以及标准LINQ(对象)。但是,因为你确实有一个&#34; LINQ&#34;标签与你的&#34; LINQ-2-SQL&#34;分开tag,因为你似乎主要对结果感兴趣(而不是在数据库中注册更改),因为当我搜索LINQ等效的SQL Server时,这是唯一真正相关的资源。 s&#34; Rollup&#34;分组功能,我会为今天有类似需求的人提供我自己的替代解决方案。
基本上,我的方法是创建一个&#34; .GroupBy()。ThenBy()&#34;可链接语法类似于&#34; .OrderBy()。ThenBy()&#34;句法。我的扩展程序需要一组IGrouping对象 - 从运行&#34; .GroupBy()&#34;得到的结果作为其来源。然后,它会收集并取消组合,以便在分组之前返回原始对象。最后,它根据新的分组函数重新分组数据,生成另一组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);}
}
您可以通过将常量值放入&#34; .ThenBy()&#34;的相应区域来使用该方法来匹配SQL Server的汇总逻辑。功能。我更喜欢使用null值,因为它是最灵活的转换常量。转换非常重要,因为您在.GroupBy()和.ThenBy()中使用的函数必须生成相同的对象类型。使用&#34; dataItems&#34;您在8月31日&09; 09的第一个回复中创建的变量,它看起来像这样:
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
您可以替换&#34; .ThenBy()&#34;中的空值。逻辑与&#34;所有&#34;,如你所愿。
您可以在&#34; .ThenBy()&#34;的帮助下模拟SQL Server的分组集,也可能是多维数据集。此外,&#34; .ThenBy()&#34;对我来说工作正常,而且我不会怀疑任何名称等同于&#34; .ThenBy()&#34; &#34; .OrderBy()&#34;方法,因为他们有不同的签名,但如果有麻烦,你可能要考虑命名它&#34; .ThenGroupBy()&#34;区分。
如前所述,我没有使用Linq-to-SQL,但我确实使用了F#的类型提供程序系统,据我所知,在许多方面都使用了Linq-to-SQL。所以我在我的F#项目中尝试了对这样一个对象的扩展,它按照我的预期工作。虽然我完全不知道这是否意味着在这方面有什么有趣的事情。
答案 3 :(得分:0)
这里提供了非常有趣的解决方案
https://blogs.msdn.microsoft.com/mitsu/2007/12/21/playing-with-linq-grouping-groupbymany/
它描述了如何通过几个属性执行groupbby。即:
var result = customers.GroupByMany(c => c.Country, c => c.City);
结果,您将获得可以简单地转换为平面列表的分层结构。