linq學習聖經

   linq是Language Integrated QueryLINQ,語言集成查詢)屬於net平臺。經過LINQ,咱們能夠使用相同API操做不一樣的數據源。linq項目,包括linq威客系統交易系統開發,學生管理系統等。 數據庫

linq使用知識 編程

     隱式類型、匿名類型、對象初始化器 c#

1)        隱式類型,使用var關鍵字建立,C#編譯器會根據用於初始化局部變量的初始值推斷出變量的數據類型。(不過我我的認爲,能用具體類型的地方儘可能不要用var關鍵字,由於這樣會讓你遺忘「被封裝類庫」方法的返回值類型--有損可讀性) 數組

隱式類型使用限制: 框架

a)        隱式類型只能應用於方法或者屬性內局部變量的聲明,不能使用var來定義返回值、參數的類型或類型的數據成員。 函數式編程

b)       使用var進行聲明的局部變量必須賦初始值,而且不能以null做爲初始值。 函數

2)        匿名類型,只是一個繼承了Object的、沒有名稱的類。C#編譯器會在編譯時自動生成名稱惟一的類。 性能

3)        對象初始化器,提供一種很是簡潔的方式來建立對象和爲對象的屬性賦值。(相關還有「集合初始化器」) this

 

因爲C#強類型語言,即咱們在聲明變量時必須指定變量的具體類型。因此在建立匿名對象時,須要結合隱式類型、匿名類型、對象初始化器一塊兒建立匿名對象。(避免類型轉換) lua

       example

              var person = new { name = 「heyuquan」 , age = 24 }

2.        Lambda表達式,Func委託

1)        Lambda表達式只是用更簡單的方式來書寫匿名方法,從而完全簡化.NET委託類型的使用。  

Lambda表達式在C#中的寫法是「arg-list => expr-body」,「=>」符號左邊爲表達式的參數列表,右邊則是表達式體(body)。參數列表能夠包含0到多個參數,參數之間使用逗號分割。

2)        Func委託

       Func委託,是微軟爲咱們預約義的經常使用委託,封裝一個具備:零個或多個指定類型的輸入參數並返回一個指定類型的結果值的方法。


代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    static void Main(string[] args)
    {
        // 委託函數
        Func<string,string,string> func1 = Hello;
        // 匿名方法
        Func<string,string,string> func2 =
            delegate(string a,string b)
            {
                return "歡迎光臨個人博客" + Environment.NewLine + a +" " + b;
            };
        // Lambda表達式
        Func<string,string,string> func3 =
            (a, b) => {return "歡迎光臨個人博客" + Environment.NewLine + a +" " + b; };
 
        // 調用Func委託
        string helloStr = func2("滴答的雨",@" http://www.cnblogs.com/heyuquan/");
 
        Console.WriteLine(helloStr);
}
    static string Hello(string a,string b)
    {
        return "歡迎光臨個人博客" + Environment.NewLine + a +" " + b;
    }

擴展方法

1)        擴展方法聲明在靜態類中,定義爲一個靜態方法,其第一個參數須要使用this關鍵字標識,指示它所擴展的類型。

2)        擴展方法能夠將方法寫入最初沒有提供該方法的類中。還能夠把方法添加到實現某個接口的任何類中,這樣多個類就可使用相同的實現代碼。(LINQ中,System.Linq.Queryable.csSystem.Linq.Enumerable.cs 正是對接口添加擴展方法)

3)        擴展方法雖定義爲一個靜態方法,但其調用時沒必要提供定義靜態方法的類名,只需引入對應的命名空間,訪問方式同實例方法

4)        擴展方法不能訪問它所擴展的類型的私有成員。

代碼以下:    

1
2
3
4
5
6
7
8
9
public static IEnumerable<TSource> MyWhere<TSource>(
    this IEnumerable<TSource> source, Func<TSource,bool> predicate)
{
    foreach (TSource itemin source)
    {
        if (predicate(item))
            yield return item;
    }
}

 Yield迭代器,延遲計算

1)        Yield迭代器

在上面定義的MyWhere擴展方法中,咱們使用了yield迭代器。使咱們沒必要「顯示」實現IEnumerableIEnumerator接口。只須要簡單的使用 yield 關鍵字,由 JIT 編譯器幫咱們編譯成實現 IEnumerableIEnumerator 接口的對象(即:本質仍是傳統遍歷,只是寫法上很是簡潔),就能使用foreach進行遍歷。

圖:

    

  延遲計算(Lazy evaluation

a)        定義:來源自函數式編程,在函數式編程裏,將函數做爲參數來傳遞,傳遞過程當中不會執行函數內部耗時的計算,直到須要這個計算結果的時候才調用,這樣就能夠由於避免一些沒必要要的計算而改進性能。

b)        Yield迭代器的延遲計算原理:JIT 編譯器會幫助咱們將迭代主體編譯到IEnumerator.MoveNext()方法中。從上圖foreach的執行流程來看,迭代主體是在每次遍歷執行到 in 的時候纔會調用MoveNext(),因此其迭代器耗時的指令是延遲計算的。

c)        LINQ查詢的延遲計算原理:經過給LINQ擴展方法傳遞方法委託,做爲yield迭代器的主體,讓遍歷執行到MoveNext()時才執行耗時的指令。

5.        表達式樹

表達式樹:表達式樹容許在運行期間創建對數據源的查詢,由於表達式樹存儲在程序集中。(後續在Linq to entities博文中與Queryable一塊兒解說)

 

Language Integrated QueryLINQ,語言集成查詢)

   

從這幅圖中,咱們能夠知道LINQ包括五個部分:LINQ to ObjectsLINQ to XMLLINQ to SQLLINQ to DataSetLINQ to Entities

 

程序集

命名空間

描述

LINQ to Objects

System.Core.dll

System.Linq

提供對內存中集合操做的支持

LINQ to XML

System.Xml.Linq.dll

System.Xml.Linq

提供對XML數據源的操做的支持

LINQ to SQL

System.Data.Linq.dll

System.Data.Linq

提供對Sql Server數據源操做的支持。(微軟已宣佈再也不更新,推薦使用LINQ to Entities

LINQ to DataSet

System.Data.DataSetExtensions.dll

System.Data

提供對離線數據操做的支持。

LINQ to Entities

System.Core.dll System.Data.Entity.dll

System.Linq System.Data.Objects

LINQ to Entities  Entity Framework 的一部分而且取代LINQ to SQL 做爲在數據庫上使用 LINQ 的標準機制。(Entity Framework 是由微軟發佈的開源對象-關係映射(ORM)框架,支持多種數據庫。)

目前,還能夠下載其餘第三方提供程序,例如LINQ to JSONLINQ to MySQLLINQ to AmazonLINQ to FlickrLINQ to SharePoint不管使用什麼數據源,均可以經過LINQ使用相同的API進行操做。

 

1.        怎樣區分LINQ操做時,使用的是哪一個LINQ提供程序?

LINQ提供程序的實現方案是:根據命名空間和第一個參數的類型來選擇的。實現擴展方法的類的命名空間必須是打開的,不然擴展類就不在做用域內。Eg:在LINQ to Objects中定義的 Where() 方法參數和在 LINQ to Entities中定義的 Where() 方法實現是不一樣。 

1
2
3
4
5
6
7
8
9
10
11
12
13
// LINQ to Objects:
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, Func<TSource,bool> predicate);
}
 
// LINQ to Entities
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, Expression<Func<TSource,bool>> predicate);
}

 

2.        LINQ查詢提供幾種操做語法?

LINQ查詢時有兩種語法可供選擇:查詢表達式(Query Expression)和方法語法(Fluent Syntax)。

.NET公共語言運行庫(CLR)並不具備查詢表達式的概念。因此,編譯器會在程序編譯時把查詢表達式轉換爲方法語法,即對擴展方法的調用。因此使用方法語法會讓咱們更加接近和了解LINQ的實現和本質,而且一些查詢只能表示爲方法調用。但另外一方面,查詢表達式一般會比較簡單和易讀。無論怎樣,這兩種語法是互相補充和兼容的,咱們能夠在一個查詢中混合使用查詢表達式和方法語法。

 

 如下擴展方法存在對應的查詢表達式關鍵字:WhereSelectSelectManyOrderByThenByOrderByDescendingThenByDescendingGroupByJoinGroupJoin

LINQ查詢表達式

約束

LINQ查詢表達式必須以from子句開頭,以selectgroup子句結束

 

關鍵字

功能

fromin

指定要查找的數據源以及範圍變量,多個from子句則表示從多個數據源查找數據。

注意:c#編譯器會把「複合from子句」的查詢表達式轉換爲SelectMany()擴展方法。

joininonequals

指定多個數據源的關聯方式

let

引入用於存儲查詢表達式中子表達式結果的範圍變量。一般能達到層次感會更好,使代碼更易於閱讀。

orderbydescending

指定元素的排序字段和排序方式。當有多個排序字段時,由字段順序肯定主次關係,可指定升序和降序兩種排序方式

where

指定元素的篩選條件。多個where子句則表示了並列條件,必須所有都知足才能入選。每一個where子句可使用謂詞&&||鏈接多個條件表達式。

group

指定元素的分組字段。

select

指定查詢要返回的目標數據,能夠指定任何類型,甚至是匿名類型。(目前一般被指定爲匿名類型)

into

提供一個臨時的標識符。該標識能夠引用joingroupselect子句的結果。

1)        直接出如今join子句以後的into關鍵字會被翻譯爲GroupJoininto以前的查詢變量能夠繼續使用)

2)        selectgroup子句以後的into它會從新開始一個查詢,讓咱們能夠繼續引入where, orderbyselect子句,它是對分步構建查詢表達式的一種簡寫方式。into以前的查詢變量都不可再使用)

      

模版以下:

     

下面以 LINQ to Objects 爲例,介紹LINQ中的各類查詢。

 

LINQ to Objects

LINQ to Objects 提供對內存中集合操做的支持,由程序集System.Core.dllSystem.Linq命名空間下的Enumerable靜態類提供。

 

運算符圖解:

linq代碼實例

      

            

   各類LINQ示例

1.        過濾操做符

根據條件返回匹配元素的集合IEnumerable<T>

1)        Where:根據返回bool值的Func委託參數過濾元素。

業務說明:查詢得到車手冠軍次數大於15次且是Austria國家的一級方程式賽手

1
2
3
4
5
6
7
     // 查詢表達式
     var racer =from rin Formula1.GetChampions()
                 where r.Wins > 15 && r.Country =="Austria"
                 select r;
// 方法語法
     var racer = Formula1.GetChampions().Where(r => r.Wins > 15
         && r.Country =="Austria");

2)        OfType<TResult>:接收一個非泛型的IEnumerable集合,根據OfType泛型類型參數過濾元素,只返回TResult類型的元素。

業務說明:過濾object數組中的元素,返回字符串類型的數組。

1
2
object[] data = {"one", 2, 3,"four","five", 6 };
var query = data.OfType<string>();// "one", "four", "five"

3)        Distinct:刪除序列中重複的元素。

 

2.        投影操做符

1)        Select 將序列的每一個元素通過lambda表達式處理後投影到一個新類型元素上。(與SelectMany不一樣在於,若單個元素投影到IEnumerable<TResult>Select不會對多個IEnumerable<TResult>進行合併)

       API

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

2)         SelectMany

a)        c#編譯器會把「複合from子句」的查詢表達式轉換爲SelectMany()擴展方法。

b)        將序列的每一個元素通過lambda表達式處理後投影到一個 IEnumerable<TResult>,再將多個IEnumerable<TResult>序列合併爲一個返回序列IEnumerable<TResult>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static IEnumerable<TResult> SelectMany<TSource
   , TResult>(this IEnumerable<TSource> source
   , Func<TSource, IEnumerable<TResult>> selector);
 
   //示例:
   string[] fullNames = {"Anne Williams","John Fred Smith","Sue Green" };
 
   IEnumerable<string> query = fullNames.SelectMany(name => name.Split());
   foreach (string namein query)
      Console.Write(name +"|");
   // Anne|Williams|John|Fred|Smith|Sue|Green|
 
   //若是使用Select,則須要雙重循環。
   IEnumerable<string[]> query = fullNames.Select(name => name.Split());
   foreach (string[] stringArrayin query)
      foreach (string namein stringArray)
         Console.Write(name +"|");
   // Anne|Williams|John|Fred|Smith|Sue|Green|

c)        將序列的每一個元素通過lambda表達式處理後投影到一個 IEnumerable<TCollection>,再將多個IEnumerable<TCollection>序列合併爲一個返回序列IEnumerable<TCollection>,並對其中每一個元素調用結果選擇器函數。

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

示例:

業務說明:(Racer類定義了一個屬性CarsCars是一個字符串數組。)過濾駕駛Ferrari的全部冠軍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 查詢表達式
    var ferrariDrivers =from rin Formula1.GetChampions()
                           from cin r.Cars
                           where c =="Ferrari"
                           orderby r.LastName
                           select r.FirstName +" " + r.LastName;
// 方法語法
    var ferrariDrivers = Formula1.GetChampions()
          .SelectMany(
              r => r.Cars,
              (r, c) =>new { Racer = r, Car = c }
          )
          .Where(r => r.Car =="Ferrari")
          .OrderBy(r => r.Racer.LastName)
          .Select(r => r.Racer.FirstName +" " + r.Racer.LastName);<br><br>

3.        排序操做符

1)        OrderBy<TSource,TKey>OrderByDescending<TSource,TKey>:根據指定鍵按升序或降序對集合進行第一次排序,輸出IOrderedEnumerable<TSource>

2)        ThenBy<TSource,TKey>ThenByDescending<TSource,TKey>:只會對那些在前一次排序中擁有相同鍵值的elements從新根據指定鍵按升序或降序排序。輸入IOrderedEnumerable <TSource>

業務說明:獲取車手冠軍列表,並依次按照Country升序、LastName降序、FirstName升序進行排序。

1
2
3
4
5
6
7
8
9
// 查詢表達式
    var racers =from rin Formula1.GetChampions()
                 orderby r.Country, r.LastNamedescending, r.FirstName
                 select r;
// 方法語法
    var racers = Formula1.GetChampions()
        .OrderBy(r => r.Country)
        .ThenByDescending(r => r.LastName)
        .ThenBy(r => r.FirstName);

3)        Reverse<TSource>:反轉集合中全部元素的順序。

 

4.        鏈接操做符

先準備兩個集合,以下:racers表示在19581965年間得到車手冠軍的信息列表;teams表示在19581965年間得到車隊冠軍的信息列表

1
2
3
4
5
6
7
8
9
10
11
12
var racers =from rin Formula1.GetChampions()
             from yin r.Years
             where y > 1958 && y < 1965
             select new
             {
                 Year = y,
                 Name = r.FirstName +" " + r.LastName
             };
 
var teams = Formula1.GetContructorChampions()
    .SelectMany(y => y.Years, (t, y) =>new { Year = y, Name = t.Name })
    .Where(ty => ty.Year > 1958 && ty.Year < 1965);

      

注意:joinon…關鍵字後的相等使用equals關鍵字。

 

1)        Join:基於匹配鍵對兩個序列的元素進行關聯。

API:

1
2
3
4
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer, IEnumerable<TInner> inner
    , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
    , Func<TOuter, TInner, TResult> resultSelector);

 

業務說明:返回19581965年間的車手冠軍和車隊冠軍信息,根據年份關聯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//  查詢表達式
var racersAndTeams =from rin racers
                     join tin teamson r.Yearequals t.Year
                     select new
                     {
                         Year = r.Year,
                         Racer = r.Name,
                         Team = t.Name
                     };
 
// 方法語法
var racersAndTeams = racers.Join(teams
        , r => r.Year, t => t.Year
        , (r, t) =>new { Year = r.Year, Racer = r.Name, Team = t.Name }
    );

 

2)        GroupJoin:基於鍵相等對兩個序列的元素進行關聯並對結果進行分組。常應用於返回「主鍵對象-外鍵對象集合」形式的查詢。

API

1
2
3
4
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer, IEnumerable<TInner> inner
    , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
    , Func<TOuter, IEnumerable<TInner>, TResult> resultSelector);

 

業務說明:返回19581965年間的車手冠軍和車隊冠軍信息,根據年份關聯並分組

注意:直接出如今join子句以後的into關鍵字會被翻譯爲GroupJoin,而在selectgroup子句以後的into表示繼續一個查詢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    // 查詢表達式
    var racersAndTeams =from rin racers
                         join tin teamson r.Yearequals t.Year
                         into groupTeams
                         select new
                         {
                             Year = r.Year,
                             Racer = r.Name,
                             GroupTeams = groupTeams
                         };
 
// 方法語法
    var racersAndTeams = racers
        .GroupJoin(teams
            , r => r.Year, t => t.Year
            , (r, t) =>new { Year = r.Year, Racer = r.Name, GroupTeams = t }
        );

3)        joinonequals…支持多個鍵關聯

可使用匿名類型來對多個鍵值進行Join,以下所示:

                from x in sequenceX

                join y in sequenceY on new { K1 = x.Prop1, K2 = x.Prop2 }

                equals new { K1 = y.Prop3, K2 = y.Prop4 }

                ...

兩個匿名類型的結構必須徹底一致,這樣編譯器會把它們對應到同一個實現類型,從而使鏈接鍵值彼此兼容。

整理Linq to Objects中運算符延遲計算特性

按字母順序整理:

具備延遲計算的運算符

CastConcatDefaultIfEmptyDistinctExceptGroupByGroupJoinIntersect 
JoinOfTypeOrderByOrderByDescendingRepeatReverseSelectSelectMany
Skip 
SkipWhileTakeTakeWhileThenByThenByDescendingUnionWhere
Zip

當即執行的運算符

AggregateAllAnyAverageContainsCountElementAtElementAtOrDefault 
EmptyFirstFirstOrDefaultLastLastOrDefaultLongCountMaxMin
Range 
SequenceEqualSingleSingleOrDefaultSumToArrayToDictionaryToList
ToLookup

特殊的AsEnumerable運算符,用於處理LINQ to Entities操做遠程數據源,將IQueryable遠程數據當即轉化爲本地的IEnumerable集合。若AsEnumerable接收參數是IEnumerable內存集合則什麼都不作。

end
相關文章
相關標籤/搜索