欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

C#入门必知:LINQ查询函数全梳理

最编程 2024-08-10 16:13:06
...
[TOC]

 

1、LINQ 函数

1.1、查询结果过滤 :where()


Enumerable.Where() 是LINQ 中使用最多的函数,大多数都要针对集合对象进行过滤,因此Where()在LINQ 的操作上处处可见,Where()的主要任务是负责过滤集合中的数据:其原型如下:

1 public static IEnumerbale<TSouce> Where<TSource>(this IEnumerable<Tsource> source,Func<TSource,bool> predicate);
2 public static IEnumerable<TSource>where<TSource> (this IEnumerable<TSource> source,Func<TSource,int,bool> predicate);

 

 Where()的参数是用来过滤元素的条件,它要求条件必须传回bool,以确定此元素是否符合条件,或是由特定的元素开始算起(使用Func<TSource,int bool>,中间的传入参数代表该元素在集合中的索引值),例如要在一个数列集合中找出大于5的数字时:

1 List<int> list1=new List<int>(){6,4,2,7,9,0};
2  
3 list1.Where(c=>c>5);

 

或者

1 list1.Where(c=>c>=1).Where(c=>c<=5);
2  
3 list1.Where(c=>c>=1&&c<=5); 

 

 Where()的判断标准是,只要判断函数返回true 就成立,反之则取消。

1.2、选取数据: Select()、SelectMany()


     通常在编写LINQ 函数调用时较少用到选取数据的函数(因为函数调用会直接返回IEnumerable<T> 集合对象 ),但在编写LINQ语句时十分常用,在语句中若编写了select new指令,它会被编译器转换成LINQ 的Select(),Select() 的原型如下:

1 public static IEnumerable<TResult> Select<TSource,TResult>(this IEnumerable<TSource> source,Func<TSource,TResult> selector);
2 public static IEnumerable<TResult> Select<TSource,TResult>(this IEnumerable<TSource> source,Func<TSource,int,TResult> selector);

 

      与Where()类似,Select() 也可以按照元素所在的位置判断处理,而Select()所指定的处理式selector 必须传回一个对象,这个对象可以是现有的类型,也可以是匿名的类型,既可以通过Select() 来重新组装所需数据。例:
1 var query=db.OrderDetails.Where(o=>o.ID==12345).Select(o=>new{ ProductID=o.ProductID,Qty=o.Qty});

 

      Select()的另一个相似函数SelectMay()则是处理有两个集合对象来源的数据选取,其原型如下:

1     public static IEnumerable<TResult> SelectMany<TSource,TResult>(this IEnumerable<TSource> source,Func<TSource,IEnumberable<TResult>> selector);
2     public static IEnumerable<TResult> SelectMany<TSource,TResult>(this IEnumerable<TSource> source,Func<TSource,int,IEnumberable<TResult>> selector);
3     public static IEnumerable<TResult> SelectMany<TSource,TCollection,TResult>(this IEnumerable<TSource> source,Func<TSource,IEnumberable<TCollection>> collectionSelector,Func<TSource,TCollection,TResult> resultSelector);
4     public static IEnumerable<TResult> SelectMany<TSource,TCollection,TResult>(this IEnumerable<TSource> source,Func<TSource,int,IEnumberable<TCollection>> collectionSelector,Func<TSource,TCollection,TResult> resultSelector);

 

      SelectMany() 在LINQ 函数调用上较难理解,但如果把它想象成数据库

CROSS JOIN ,相对来说就容易懂了,例: 

1 List<int> list1=new List<int>(){1,2,3,4,5,6};
2 List<int> list2=new List<int>(){6,4,2,7,9,0};
3  
4 var query=list1.SelectMany(o=>list2);
5 foreach(var q in query)
6      Console.WriteLine("{0}",q);

 

      输出结果:

1 6424790642790642790642790642790642790

 

      因为“642790”输出了 6次,list1 内的元素是6个,所以可以知道SelectMany()会按照list1 内的元素个数调用它的selector,并组装集合输出。

 

1.3、群组数据:GroupBy()、ToLookup()


     汇总数据是查询机制的基本功能,而在汇总之前,必须要先将数据做群组化,才能进行统计,LINQ 的群组数据功能由Enumerable.GroupBy()函数提供。

     GroupBy() 会按照给定的key(keySelector)以及内容(elementSelector),产生群组后的结果(IGroup接口对象或是由resultSelector生成的结果对象),例:

 1 List<int> sequence =new List<int>(){1,2,3,4,3,2,4,6,4,2,4};
 2  
 3 var group=sequence.GroupBy(o=>o);
 4 foreach(var g in group)
 5 {
 6      Console.WrilteLine("{0} count:{1}",g.Key,g.Count());
 7 /*计算每个数出现的次数。
 8      GroupBy 设置了使用数列本身值作为Key值,并且利用这个Key 分组产生分组的数据(IGrouping<TKey,TElement类型),再对分组的数据进行汇总。结果如下:
 9  
10 1 count: 1
11 2 count: 3
12 3 count: 2
13 4 count: 4
14 6 count: 1
15 
16 */

 

     若是想要在返回之前对分组后的元素做处理,可以传入elementSelector 而若是要在元素处理后产生结果的话,则可以传入resultSelector,这样返回的集合会是以resultSelector 返回的类型为主,而不是默认的IGroup接口。

     除了GroupBy()能群组化数据外、另外一个具有群组化数据能力的是ToLookUp(),它可以生成具有群组化特性的集合对象,由ILookup<TKey,TElement>组成。

    ToLookup()看起来和GroupBy()有些类似,但是它会另外生成一个新的集合对象,这个集合对象由ILookup<TKey,TElement>所组成,允许多个键值存在,且一个键值可包含许多关联的实值。例:

 1 var nameValuesGroup=new[]
 2 {
 3      new{name="Allen", value=65,group="A"},
 4      new{name="Abbey",value=120,group="B"},
 5      new{name="Sue",Value=200,group="A"}
 6 };
 7 var lookupValues=namValuesGroup.ToLookup(c=>c.group);
 8 foreach(var g in lookupValues)
 9 {
10      Console.WriteLine("===Group: {0}===",g.Key);
11      foreach(var item in g)
12      {
13           Console.WriteLine("name:{0},value:{1}",item.name,item.value);
14      }
15 }

 

      GroupBy()本身具有延迟执行的特性,而ToLookup()没有。

1.4、联接数据: Join() 与GroupJoin()


     身为一个查询机制,将两个集合进行联接(join)也是理所当然的,尤其是在进行数据的对比和汇总时,联接机制显得更重要。在LINQ 函数中,有 Enumerable.Join() 函数负责处理联接,其原型如下:

public static IEnumerable<TResult> Join<TOuter,TInner,TKey,TResult>(this IEnumerable<TOuter> outer,IEnumerable<TInner> inner,Func<TOutput,TKey> outerKeySelector,Func<TInner,TKey> innerKEySelector,Func<TOuter,TInner,TResult> resultSelector)
   

 

 

     由原型可看到它将原本的集合视为TOuter,而将传入的集合视为TInner,儿还要决定由哪个属性或成员当Key,最后由resultSelector来输出联接的结果。例:

1 var query=from item1 in list1
2      join item2 in list2 on item1 equals item2
3      select item2;
4 var query3=list1.Join(
5      list2,
6      item1=>item1,
7      item2=>item2,
8      (item1,item2)=>item2
9      );

 

     Enumerable<T>.Join()使用的是INNER JOIN的概念,当TInner.KeyTOuter.Key相同时,才会将元素输出到resultSelector 作为参数。

     目前常用的联接模式,INNER JOINEnumerable<T>.Join() 实现,CROSS JOINEnumerable<T>.SelectMany() 实现,还有一种JOIN 模式没有考虑,LEFT OUTER JOIN模式,要实现这个模式,必须要借助GroupJoin()方法来实现。

    GroupJoinJoin() 十分相似,不过它却又Join()GroupBy()两者的功能,在Join() 的情况下,它会留下TInnerTOuter两边都有的值,但在GroupJoin(),它会将TOuter 的值作为Key,并依此来对TInner 做群组化后输出,例:

1 var query4=from item1 in list1
2           join item2 in list2 on item1 equals item2 into g
3           from item in g.DefaultIfEmpty()
4           select new{ v=item1,c=item};
5 var query5=list1.GroupJoin(
6           list2,
7           item1=>item1,
8           item2=>item2,
9           (item1,item2)=>new {v=item1,c=item2.Count()});

 

 

1.5、数据排序:OrderBy() 与ThenBy()


     数据排序是在数据处理中常见的功能,在LINQ 内的排序主要是以OrderBy函数为主,而为了支持连续条件的排序,可加上ThenBy 函数,以便处理多重条件排序的需求。基于LINQ的延迟查询机制,排序也不是在一开始就进行的,而是在数据真的被访问时才会进行排序。因此OrderBy()在处理集合时,传递回来的是称为IOrderedEnumerable<T> 接口的对象。

     OrderByThenBy还有一个相似的方法,差别只在于做反向排序。OrderByDescendingThenByDescending

     观察函数的原型,会发现OrderBy传入的是IEnumerable<T> ,但ThenBy传入的是IOrderedEnumerable,所以一般在排序时先调用OrderBy,再使用ThenBy进行多重排序。假设一个集合有A和B两个属性,如果想要先为A排序再为B排序,则要使用OrderBy(A).ThenBy(B)的方式来进行排序,OrderByThenBy一次调用只能设置一个字段,在进行多重条件时,必须先调用OrderBy,再按需求调用ThenBy一次或多次。例:

 1  var nameValues=new[]
 2 {
 3      new {name="Allen",value=64},
 4      new {name="abbey",value=120},
 5      new {name="slomng",value=330},
 6      new {name="george",value=213}
 7 };
 8 //single sort
 9 var sortedNames=nameValues.OrderBy(c=>c.name);
10 var sortedValues=nameValues.OrderBy(c=>c.value);
11  
12 //multiply sort conditions
13 var sortedByNameValues=nameValues.OrderBy(c=>c.name).ThenBy(c=>c.value);
14 var sortedByValueNames=nameValues.OrderBy(c=>c.value).ThenBy(c=>c.name);

 

      如果要设置多重排序条件,请务必使用OrderBy()加上ThenBy()的组合,若使用OrderBy +OrderBy 组合,会使得排序被执行两次,最终的结果会是最后一个OrderBy 所产生的的结果。

1.6、获取集合


     LINQ 所处理的数据都由集合而来,因此将LINQ 执行的结果转换成集合也很容易。LINQ本身支持四种不同的集合生成方式,包含生成数组的ToArray()、生成列表的ToList、生成字典集合的ToDictionary 以及生成Lookup<TKey,TElement>类的ToLookup。例:

1 var arrayOutput=nameValues.ToArray();
2 var listOutput=nameValues.ToList();
3  
4 var dictOutput1=nameValues.ToDictionary(c=>c.name);
5 var dictOutput2=nameValues.ToDictionary(c=>c.name,c=>value);
 

 

1.7、划分并获取集合


     Skip()SkipWhile()Take()TakeWhile()。在数据库查询时,为了达到最佳的性能,在数据量大时要进行分页处理(paging)。上面四个函数的功能就是在大集合内切出少量数据。

 1 public static IEnumberable<TSource> Skip<TSource>(
 2      this IEnumerable<TSource> source,
 3      int count
 4 )
 5 public static IEnumberable<TSource> SkipWhile<TSource>(
 6      this IEnumerable<TSource> source,
 7      Func<TSource,bool> predicate
 8 )
 9 public static IEnumberable<TSource> SkipWhile<TSource>(
10      this IEnumerable<TSource> source,
11      Func<TSource,int ,bool> predicate
12 )
13 public static IEnumberable<TSource> Take<TSource>(
14      this IEnumerable<TSource> source,
15      int count
16 )
17 public static IEnumberable<TSource> TakeWhile<TSource>(
18      this IEnumerable<TSource> source,
19      Func<TSource,bool> predicate
20 )
21 public static IEnumberable<TSource> TakeWhile<TSource>(
22      this IEnumerable<TSource> source,
23      Func<TSource,int ,bool> predicate
24 )

 

      Skip()用来在集合中跳跃,让LINQ 核心直接将游标跳到指定的位置,而不用通过“巡航”来移动,在大型集合中可节省不少时间,而SkipWhile 也有相同作用,但多了判断式,也就是跳过符合条件的元素,而不同的SkipWhile()可用来决定要跳过符合条件的或是判断跳过特定的索引值。

     Take()用来传回集合中特定数量的元素,它会告知LINQ 核心直接返回它所指定的元素数量,很适合使用与分页的功能。TakeWhile 则是和SkipWhile 类似都是多了条件判断式,不过TakeWhile 在元素满足条件时,就返回该元素或是符合特定的索引值条件时返回该元素。

 

1.8、访问元素


     IEnumerable<T>本身就是集合对象,所以针对集合对象所需要的元素访问也是必要的功能,LINQ里的元素访问功能是判断容器内是否含有元素等。

     首先是获取首尾的元素,分别由First() 以及Last()两个方法负责,它们还各有一个姐妹方法FirstOrDefault()以及LastOrDefault()前者若没有第一个或最后一个元素时,会传回null,而后者会传回其类型的默认值(基本上就是default(T)的结果)。

     FirstOrDefault() 以及 LastOrDefault()都没有提供默认的设置方式,因此若想要使用非default(T)的默认值,要使用DefaultEmpty()来设置。First()Last() 都能传入判断元素是否符合条件的参数,当条件判断存在时,First 会从集合的前面开始扫描,并返回扫描到符合条件的第一个元素,Last 则是反过来从集合的尾端开始扫描,并返回扫描到符合条件的第一个元素。例:

1 var firstLastItems=new []{"zero","two","three","four","five"};
2 string firstContainsO=firstLastItems.First(s=>s.Contains('o'));
3 string lastContainsO=firstLastItems.Last(s=>s.Contains('0'));

 

LINQ 内还有一个Single,他会在集合中只有一个元素时传回该元素,但若集合是空的或是有两个以上的元素时会调用例外处理,或是使用它的姐妹方法SingleOrDefault 传回null值,实用性比fisrt和last 低。

     LINQ 提供了ElementAt()这个方法,可按照索引值访问元素,他有个相似方法ElementAtOrDefault作用和firstordefault/lastordefault 是相同的。当找不到元素时就返回默认值。例:

1 var firstLastItems=new []{"zero","two","three","four","five"};
2 string itematThree=firstLastITems.ElementAt(2);

 

      若要判断集合内有没有特定值,LINQ 提供了Contains, 可以判断集合捏有没有传入的元素,但因为Contain 会判断对象是否相等,所以它另外提供了一个可传入IEqualityComparer<T> 的作为比较依据的重载(overload)方法,可用于自定义类对象的相等比较操作。

     若要判断集合内有没有值,LINQ 提供了两个方法,一个是Count(), 另一个是Any(),除了可以简单判断集合内有没有值外,也可以传入判断条件来决定是否要列入计算。通常会习惯使用Count 来判断集合内是否存在任何元素,为什么要多做一个Any呢。其实是考虑到LINQ 可能的查询对象会包含远程数据库,不一定只有本地的数据源。对于远程的数据源,如果使用Count ,要花费较高的成本来读取数据后进行计数在传回,但若是使用Any(),则远程只要判断符合条件的数据是否存在一笔即可,不需要完整计数,所以针对远程数据源,使用Any 来判断有无数据是较好的选择。针对本地的集合 any 和count 几乎没有差异。

     若要判断集合内的元素是否全部符合特定条件时, 可以利用LINQ 的All(), 它可以按照传入的条件来扫描所有元素,只有在所有元素都符合条件时,或是集合时空时才会返回true ,否则会返回false。

     若要按照元素的类型进行筛选的话,除了使用Where 对每个元素做类型信息判断外,LINQ 也提供了一个更简便的方法 OfType<T>(),它可以传回集合内符合T所指定类型的信息,这个方法很适合用在集合内包含了已实现了许多接口的类对象。然后使用OfType<T>按照接口类型进行筛选。

     OfType<T>还有一个类似方法Cast<T> ,功能与OfType <T>相同,但Cast<T>会试图把集合内的元素类型转换成T类型,若无法进行类型转换时会调用InvalidCastException 例外处理。若使用OfType<T>则不会引发例外处理。

 

1.9、聚合与汇总


     聚合运算(aggregation)是集合数据处理的重要功能之一,基本的Max ,Min ,Sum ,Average 以及可自己制定聚合规则的Aggregate()

     Aggregate 是可暂存每一步计算结果的方法,它允许程序员按照传入的条件对每个集合内的元素进行计算,而在每次调用时,他都会将前一次的结果暂存起来,并作为下次计算的传入参数。Aggregate 基本上做到三种工作,第一种是直接按照传入的条件来处理累计运算;第二种是可在调用时传入一个种子值(seed),这个种子值会在开始进行运算时作为基准使用,之后可按照第一次对种子值的运算方式开始做累计运算;第三种则是在传回之前做最后的处理,例:

 1 double myBalance=100.0;
 2  
 3 int[] withdrawItems={20,10,40,50,10,70,30};
 4  
 5 double balance=withdrawItems.Aggregate(myBalance,(originbalance,nextWithdrawal)=>{
 6      Console.WriteLine("originbalance:{0},nextWithdrawak:{1}",originbalance,nextdrawal);
 7      Console.WriteLine("Withdrawal status:{0}",(nextWithdrawal<=originbalance)?"OK":"FAILED");
 8  
 9      return ((nextWithdrawal<=originbalance)?(originbalance-nextWithdrawal):originbalance);
10 });
11 Console.WriteLine("Ending balance:{0}:",balance);

 

 若要对最终的存款值进行处理,即可使用第三个参数resultSelector,例:

 

1 var balanceStatus=
2 withdrawItems.Aggregate(myBalance,(originbalance,nextWithdrawal)=>{
3      return((nextWithdrawal<=originbalance)?(originbalance-nextWithdrawal):originbalance);
4  
5 },
6 (finalbalance)=>
7 {
8      return (finalbalance>=1000)?"Normal":"Lower";
9 });

 

 

2、标准的查询操作符

 

2.1 筛选


    例:找出赢得至少15场比赛的碧玺和奥地利赛车手。代码如下:

1 var racers=from r in Formula1.GetChampions()
2           where r.Wins > 15 && (r.Country=="Brazil"||r.Country=="Austria")
3           select r;
4  
5 foreach(var r in racers)
6 {
7      Console.WriteLine("{0:A}",r);
8 }

 

      下面使用Where()  和 Select() 的代码:

1 var racers=Formula1.GetChampions().
2           Where(r=>r.Wins>15 && (r.Country=="Brazil" || r.Country=="Austria")).
3           Select(r=>r);

 

 

2.2 用索引筛选


     不能使用LINQ 查询的一个例子是Where 方法的重载。在Where 方法的重载中,可以传递第二个参数——索引。索引是筛选器返回每个结果的计数器。可以在表达式中使用这个索引, 执行基于索引的计算。下面的代码由Where 扩展方法调用,它使用索引返回姓氏以“A” 开头,索引为偶数的赛车手。

1 var racers=Formula1.GetChamptions().
2           Where((r,index)=>r.LastName.StartsWith("A") && index % 2 !=0);
3  
4 foreach(var r in racers)
5 {
6      Console.WriteLine("{0,A}",r);
7 }
8  

 

2.3 类型筛选


     为了进行基于类型的筛选,可以使用OfType扩展方法。这里数组数据包含string 和 int 对象。 使用OfType扩展方法,把string类传递给泛型参数,就从集合中返回字符串。

1 object[] data={"ones",1,3,"fre","fdfs",333};
 2 var query=data.OfType<string>();
 3 foreach(var s in query)
 4 {
 5      Console.WriteLine(s);
 6 }
 7  /*
 8      运行结果为:
 9  
10 ones
11 fre
12 fdfs
13 */

 

 

2.4 复合的from 子句


     如果需要根据对象的一个成员进行筛选,而该成员本身是一个系列,就可以使用复合的from 子句。Racer 类定义了一个属性Cars,其中Cars 是一个字符串数组。要筛选驾驶法拉利的所有冠军,可以使用如下所示的LINQ 查询。第一个from子句访问从Formula1.GetChampion()方法返回的Race 对象,第二个from 子句访问Racer的 Cars 属性。以返回所有string 类型的赛车。接着在where 子句中使用这些赛车筛选驾驶法拉利的所有冠军。

1 var ferrariDrivers=from r in Formula.GetChampions()
2                     from c in r.Cars
3                     where c=="Ferrari"
4                     orderby r.LastName
5                     select r.FirstName +" "+ r.LastName;

 

     C# 编译器把符合的from 子句和LINQ 查询转换为SelectMany 扩展方法。其中实例所用的重载版本如下

1 public static IEnumerable<TResult> SelectMany<TSource,TCollection,TResult>(this IEnumerable<TSource> source,Func<TSource,IEnumberable<TCollection>> collectionSelector,Func<TSource,TCollection,TResult> resultSelector);

 

     第一个参数是隐式参数,它从 Get.Champions()方法中接收Racer 对象序列。第二个参数是collectionSelector委托,其中定义了内部序列。在Lambda 表达式 r=>r.Cars 中,应返回赛车集合。第三个委托参数是一个委托,现在为每个赛车调用给委托,接收Racer 和Car 对象。Lambda 表达式创建了以匿名类型,他有Racer 和 Car 类型。 这个SelectMany方法的结果是摊平了赛车手和赛车的层次结构,为每辆赛车返回匿名类型的一个新对象集合。

 

1 var ferrariDrivers= Formula1.GetChampion().
2                     SelectMany(r=>r.Cars,
3                          (r,c)=>new{Racer=r,Car=c}.
4                          where(r=>r.Car=="Ferrari").
5                          OrderBy(r=>r.Racer.LastName).
6                          Select(r=>r.Racer.FirstName+" "+r.Racer.LastName));

 

2.5 排序


要对序列排序,前面使用了 orderby 子句。下面复习一下前面使用的orderby descending 子句的例子。其中赛车手按照赢得比赛的次数进行降序排序,赢得比赛的次数用关键字选择器指定。

1 var racers=from r in Formula1.GetChampions()
2            where r.Country=="Brazil"
3            orderby r.Wins descending
4            select r;

 

     orderby 子句解析为OrderBy( ) 方法,orderby descending子句解析为OrderByDescending方法

 

1 var racers= Formula1.GetChampions().
2           Where(r=>r.Country=="Brazil").
3           OrderByDescending(r=>r.Wins).
4           Select(r=>r);

 

     使用LINQ 查询时,只需把所有用于排序的不同关键字(用逗号隔开)添加到orderby子句中。在下例中,所有的赛车手先按照国家排序,再按照姓氏排序,最后按照名字排序。添加到LINQ 查询结果中的Take()扩展方法用于提取前十个结果:

 

1 var racers=(from r in Formula1.GetChampions()
2                orderby r.Country,r.LastName,r.FirstName
3                select r).Take(10);

 

     使用OrderByThenBy扩展方法可以执行相同的操作

1 var racers=Formula1.GetChamptions().
2      OrderBy(r=>r.Country).
3      ThenBy(r=>r.LastName).
4      ThenBy(r=>r.FirstName).
5      Take(10);

 

 

2.6 分组


     要根据一个关键字值对查询结果分组,可以使用group子句。 现在一级方程式冠军应该按照国家分组,并列出一个国家的冠军数。子句group r by r.County into g根据 Country 属性组合所有的赛车手,并定义一个新的标识符g, 它以后用于访问分组的结果信息。group子句的结果应该根据应用到分组结果上的扩展方法Count来排序,如果冠军数相同,就根据关键字排序,该关键字是国家,因为这是分组使用的关键字。where 子句根据至少有两项的分组来筛选结果。select 子句创建一个带CountryCount属性的匿名类型。

 

 1 var countries= from r in Formula1.GetChampions()
 2                group r by r.Country into g
 3                orderby g.Count() descending, g.Key
 4                where g.Count() >=2
 5                select new {
 6                               Country=g.Key,
 7                               Count=g.Count()
 8                          };
 9 foreach(var item in
						

上一篇: 速成LINQ指南:30分钟轻松掌握

下一篇: 使用Linq to Entity进行多条件OR查询的技巧