一、概述
查询运算符是组成Linq模式的方法。这些方法中的大多数都作用于序列;其中序列指其类型实现IEnumberable<T>接口或IQueryable<T>接口的对象。标准查询运算符提供包括筛选、投影、集合、排序等查询功能。
查询运算符包含两组,一组作用于类型IEnumberable<T>的对象,另一组作用于类型IQueryable<T>的对象。
《Linq查询运算符(一)》详细介绍了筛选数据、投影运算、设置运算 、数据排序、限定符运算及数据分区等
《Linq查询运算符(二)》详细介绍了生成运算、相等运算、元素运算、转换数据类型等,
本文将继续介绍Linq运算符串联运算、聚合运算、联接运算及数据分组。
二、串联运算
2.1 Enumerable.Concat
连接两个序列。
public static System.Collections.Generic.IEnumerable<TSource> Concat<TSource> (this System.Collections.Generic.IEnumerable<TSource> first, System.Collections.Generic.IEnumerable<TSource> second);
参数类型
TSource:输入序列中的元素的类型。
参数
first IEnumerable
要连接的第一个序列
second IEnumerable
要与第一个序列连接的序列
返回
一个包含两个输入序列的连接元素的IEnumerable
Person[] parameters = new Person[] { new Person() { Id = 1, Code = "052", Name = "正一郎" }, new Person() { Id = 2, Code = "028", Name = "正贰郎" }, new Person() { Id = 3, Code = "020", Name = "正叁郎" } }; Person[] parameters1 = new Person[]{ new Person() { Id = 1, Code = "030", Name = "次一郎" }, new Person() { Id = 2, Code = "031", Name = "次二郎" }, new Person() { Id = 3, Code = "032", Name = "次三郎" } }; var three= parameters.Select(p => p.Name).Concat(parameters1.Select(p=>p.Name)); foreach (var person in three) { Console.WriteLine(person); }
输出结果
正一郎 正次郎 誠三郎 次一郎 次贰郎 次叁郎
三、聚合运算
聚合运算从值的集合中计算出单个值。
3.1 Aggregate
对序列应用累加器函数。 将指定的种子值用作累加器的初始值,并使用指定的函数选择结果值。
public static TResult Aggregate<TSource,TAccumulate,TResult> (this System.Linq.IQueryable<TSource> source, TAccumulate seed, System.Linq.Expressions.Expression<Func<TAccumulate,TSource,TAccumulate>> func, System.Linq.Expressions.Expression<Func<TAccumulate,TResult>> selector);
类型参数
TSource source的元素类型。
TAccumulate 累加器值的类型。
TResult 结果值的类型。
参数
source 要对其进行聚合的序列
seed TAccumulate累加器的初始值
func Expression<Func<TAccumulate,TSource,TAccumulate>>
要对每个元素调用的累加器函数
selector Expression<Func<TAccumulate,TResult>>
将累加器的最终值转换为结果值的函数。
返回
TResult
已转换的累加器最终值。
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" }; string result= fruits.AsEnumerable().Aggregate("", (current, next) => current + " " + next+" "); Console.WriteLine(result);
运行结果
apple mango orange passionfruit grape
3.2 Average
计算可以为 null 的数值序列的平均值,这些值可通过对输入序列的每个元素调用转换函数获得。
定义
public static T Average (this System.Collections.Generic.IEnumerable<T> source);
T为数值类型
string[] numbers = { "100", "98", "50","2" }; double average = numbers.Average(num => long.Parse(num)); Console.WriteLine("The average is {0}.", average);
运行结果
The average is 62.5.
3.3 Count
返回指定的序列中满足条件的元素数量。
定义
public static int Count<TSource> (this System.Collections.Generic.IEnumerable<TSource> source);
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" }; int fruitCount = fruits.Count(); Console.WriteLine($"水果的种类:{fruitCount}."); int fruitCount2 = fruits.Count(e=>e.IndexOf('m')==0); Console.WriteLine($"水果的种类:{fruitCount2}.");
运行结果
水果的种类:5. 水果的种类:1.
3.4 LongCount
返回序列中的元素数量的Int64
定义
public static long LongCount<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate);
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" }; long fruitCount = fruits.LongCount(); Console.WriteLine($"水果的种类:{fruitCount}."); long fruitCount2 = fruits.LongCount(e=>e.IndexOf('m')==0); Console.WriteLine($"水果的种类:{fruitCount2}.");
运行结果
水果的种类:5. 水果的种类:1.
3.5 Max 或 MaxBy
确定集合中的最大值
定义
public static TSource? Max<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, System.Collections.Generic.IComparer<TSource>? comparer);
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" }; string[] numbers = { "100", "98", "50","2" }; int maxInt = numbers.Max(x => int.Parse(x)); Console.WriteLine(maxInt); int maxLength = fruits.Max(x => x.Length); Console.WriteLine(maxLength);
运行结果
100 12
3.6 Min 或 MinBy
确定集合中的最下值
Min<TSource>(IEnumerable<TSource>, Func<TSource,Double>)
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" }; string[] numbers = { "100", "98", "50","2" }; int minInt = numbers.Min(x => int.Parse(x)); Console.WriteLine(minInt); int minLength = fruits.Min(x => x.Length); Console.WriteLine(minLength);
运行结果
2 5
3.7 Sum
对集合中的值求和。如果 source 不包含任何元素,则此方法返回零。
定义
Sum<TSource>(IEnumerable<TSource>, Func<TSource,Single>)Sum<TSource>(IEnumerable<TSource>, Func<TSource,Single>)
四、聚合运算
联接两个数据源就是将一个数据源中的对象与另一个数据源中具有相同公共属性的对象相关联。
当查询所有面向的数据源相互之间具有无法直接领会的关系时,Join就成为一项重要的运算。在面向对象的编程中,这可能意味着建模对象之间进行关联。Linq框架中提供的join方法包括Join和GroupJoin。这些方法执行同等联接。既根据2个数据源的键是否相等来匹配这2个数据源的联接。
定义测试测试类Product
class Product { public string? Name { get; set; } public int CategoryId { get; set; } }
定义测试测试类Category
class Category { public int Id { get; set; } public string? CategoryName { get; set; } }
4.1 Join
根据键选择器函数Join两个序列并提取值对。
表达式语法
Join … in … on … equals …
List<Product> products = new List<Product> { new Product { Name = "Cola", CategoryId = 0 }, new Product { Name = "Tea", CategoryId = 0 }, new Product { Name = "Apple", CategoryId = 1 }, new Product { Name = "Kiwi", CategoryId = 1 }, new Product { Name = "Carrot", CategoryId = 2 } }; List<Category> categories = new List<Category> { new Category { Id = 0, CategoryName = "Beverage" }, new Category { Id = 1, CategoryName = "Fruit" }, new Category { Id = 2, CategoryName = "Vegetable" } }; var query = from product in products join category in categories on product.CategoryId equals category.Id select new { product.Name,product.CategoryId, category.CategoryName }; foreach (var item in query) { Console.WriteLine($"{item.Name} - {item.CategoryName}"); }
运行结果
Cola - Beverage Tea - Beverage Apple - Fruit Kiwi - Fruit Carrot - Vegetable
4.1 GroupJoin
根据键选择器函数Join两个序列,并对每个元素的结果匹配项进行分组。
表达式语法
join … in … on … equals …
List<Product> products = new List<Product> { new Product { Name = "Cola", CategoryId = 0 }, new Product { Name = "Tea", CategoryId = 0 }, new Product { Name = "Apple", CategoryId = 1 }, new Product { Name = "Kiwi", CategoryId = 1 }, new Product { Name = "Carrot", CategoryId = 2 } }; List<Category> categories = new List<Category> { new Category { Id = 0, CategoryName = "Beverage" }, new Category { Id = 1, CategoryName = "Fruit" }, new Category { Id = 2, CategoryName = "Vegetable" } }; var productGroups = from category in categories join product in products on category.Id equals product.CategoryId into productGroup select productGroup; foreach (IEnumerable<Product> productGroup in productGroups) { Console.WriteLine("Group"); foreach (Product product in productGroup) { Console.WriteLine($"{product.Name,8}"); } }
五、数据分组
分组是指将数据分到不同的组,使每组中的元素拥有公共的属性。
5.1 GroupBy
对共享通用属性的元素进行分组,每组由一个IGrouping<TKey,TElement>对象表示
查询表达式语法
groupby
List<Product> products = new List<Product> { new Product { Name = "Cola", CategoryId = 0 }, new Product { Name = "Tea", CategoryId = 0 }, new Product { Name = "Apple", CategoryId = 1 }, new Product { Name = "Kiwi", CategoryId = 1 }, new Product { Name = "Carrot", CategoryId = 2 } }; List<Category> categories = new List<Category> { new Category { Id = 0, CategoryName = "Beverage" }, new Category { Id = 1, CategoryName = "Fruit" }, new Category { Id = 2, CategoryName = "Vegetable" } }; var proGroups = products.GroupBy(e => e.CategoryId); foreach (var gp in proGroups) { Console.WriteLine($"GroupByValue:{gp.Key}"); foreach (var g in gp) { Console.WriteLine($"GroupValue:{g.Name}"); } } Console.WriteLine($"=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.="); var goups = from product in products group product by product.CategoryId into productGroup select productGroup; foreach (var ps in goups) { Console.WriteLine($"GroupByKey:{ps.Key}"); foreach (var grp in ps) { Console.WriteLine($"GroupValue:{grp.Name}"); } }
5.2 ToLookup
根据指定的键选择器和元素选择器函数,从IEnumerable生成一个泛型Lookup<TKey,TElement>。
语法:
public static System.Linq.ILookup<TKey,TElement> ToLookup<TSource,TKey,TElement> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,TKey> keySelector, Func<TSource,TElement> elementSelector);
类型参数
TSource source的元素类型。
TKey keySelector返回的键的类型。
TElement elementSelector返回的值的类型。
ILookup<int, string> lookupProduct = products .ToLookup(p => p.CategoryId,p => p.Name); foreach (var vproduct in lookupProduct) { Console.WriteLine($"Key:{vproduct.Key}"); foreach (var pro in vproduct) { Console.WriteLine($"Value:{pro}"); } }
六、总结
LINQ采用表达式声明性的查询语法编写,这样可以用最少的代码进行对数据结构操作。Linq也提供丰富的操作函数,在实际的使用中根据不同应用常见选择恰当的方法。