首頁 > 軟體

C#使用LINQ查詢操作符範例程式碼(二)

2022-06-14 22:00:13

相關閱讀

C#使用LINQ查詢操作符範例程式碼(一)

C#使用LINQ查詢操作符範例程式碼(二)

六、連表操作符

1、內連線

1、使用 join 子句 根據特定的條件合併兩個資料來源,但之前要獲得兩個要連線的列表。

業務說明:返回1958到1965年間的車手冠軍和車隊冠軍資訊,根據年份關聯

var racers = from r in Formula1.GetChampions()
             from y in r.Years
             select new
             {
                 Year = y,
                 Name = r.FirstName + " " + r.LastName
             };

var teams = from t in Formula1.GetContructorChampions()
            from y in t.Years
            select new
            {
                Year = y,
                Name = t.Name
            };

var racersAndTeams0 =
      (from r in racers
       join t in teams on r.Year equals t.Year
       orderby t.Year
       select new
       {
           Year = r.Year,
           Racer = r.Name,
           Team = t.Name
       }).Take(10);

方法語法:

var racersAndTeams = racers
    .Join(teams, r => r.Year, t => t.Year, (r, t) => new { Year = r.Year, Racer = r.Name, Team = t.Name })
    .OrderBy(p => p.Year).Take(10);

結果:

Year  Champion             Constructor Title 
1958: Mike Hawthorn        Vanwall 
1959: Jack Brabham         Cooper 
1960: Jack Brabham         Cooper 
1961: Phil Hill            Ferrari 
1962: Graham Hill          BRM 
1963: Jim Clark            Lotus 
1964: John Surtees         Ferrari 
1965: Jim Clark            Lotus 
1966: Jack Brabham         Brabham 
1967: Denny Hulme          Brabham

2、或者合併成一個LINQ 查詢

var racersAndTeams =
          (from r in
               from r1 in Formula1.GetChampions()
               from yr in r1.Years
               select new
               {
                   Year = yr,
                   Name = r1.FirstName + " " + r1.LastName
               }
           join t in
               from t1 in Formula1.GetContructorChampions()
               from yt in t1.Years
               select new
               {
                  Year = yt,
                  Name = t1.Name
               }
           on r.Year equals t.Year
           orderby t.Year
           select new
           {
               Year = r.Year,
               Racer = r.Name,
               Team = t.Name
           }).Take(10);

方法語法

var racersAndTeams0 = Formula1.GetChampions()
               .SelectMany(m => m.Years, (m, y) => new { Racer = m, Year = y })
               .Join(Formula1.GetContructorChampions()
               .SelectMany(m => m.Years, (m, y) => new { Team = m, Year = y })
               , m => m.Year, m1 => m1.Year
               , (m, m1) => new
               {
                   Year = m.Year,
                   Racer = m.Racer.FirstName + " " + m.Racer.LastName,
                   Team = m1.Team.Name
               })
               .OrderBy(m => m.Year).Take(10);

2、左外連線(DefaultIfEmpty)

左外連線返回左邊序列中的全部元素,即使它們在右邊的序列中並沒有匹配的元素。

左外連線用join子句和 DefaultIfEmpty 方法定義。 使用 DefaultIfEmpty 定義其右側的預設值。

linq只支援左連線,如要右連線,將query和query1調換位置

業務說明:如賽車手比車隊設立冠軍的年份要早,可能某個年份只有賽車手冠軍沒有車隊冠軍,這時候需要左連線查詢。

var racers = from r in Formula1.GetChampions()
             from y in r.Years
             select new
             {
                 Year = y,
                 Name = r.FirstName + " " + r.LastName
             };

var teams = from t in Formula1.GetContructorChampions()
            from y in t.Years
            select new
            {
                Year = y,
                Name = t.Name
            };


var racersAndTeams =
  (from r in racers
   join t in teams on r.Year equals t.Year into rt
   from t in rt.DefaultIfEmpty()
   orderby r.Year
   select new
   {
       Year = r.Year,
       Champion = r.Name,
       Constructor = t == null ? "no constructor championship" : t.Name
   }).Take(10);

foreach (var item in racersAndTeams)
{
    Console.WriteLine("{0}: {1,-20} {2}", item.Year, item.Champion, item.Constructor);
}

結果

1950: Nino Farina          no constructor championship 
1951: Juan Manuel Fangio   no constructor championship 
1952: Alberto Ascari       no constructor championship 
1953: Alberto Ascari       no constructor championship 
1954: Juan Manuel Fangio   no constructor championship 
1955: Juan Manuel Fangio   no constructor championship 
1956: Juan Manuel Fangio   no constructor championship 
1957: Juan Manuel Fangio   no constructor championship 
1958: Mike Hawthorn        Vanwall 
1959: Jack Brabham         Cooper

3、組連線

左外連線使用了組連線和 into 子句。它有一部分與組連線相同,只不過組連線不適用 DefaultIfEmpty 方法。

使用組連線時,基於鍵相等對兩個兩個獨立的序列的元素進行關聯並對結果進行分組。

常應用於返回“主鍵物件-外來鍵物件集合”形式的查詢。

業務說明:返回1958到1965年間的車手冠軍和車隊冠軍資訊,根據年份關聯並分組

注意:直接出現在join子句之後的into關鍵字會被翻譯為GroupJoin,而在select或group子句之後的into表示繼續一個查詢。

// 查詢表示式
var racersAndTeams =( from r in racers
                     join t in teams on r.Year equals t.Year into groupTeams
                     select new
                     {
                         Year = r.Year,
                         Racer = r.Name,
                         GroupTeams = groupTeams
                     }).Take(10);

方法語法:

var racersAndTeams1 = racers
    .GroupJoin(teams, r => r.Year, t => t.Year, (r, t) => new { Year = r.Year, Racer = r.Name, GroupTeams = t }
    ).Take(10);;

foreach (var item in racersAndTeams)
{
    Console.WriteLine("{0}: {1,-20} {2}", item.Year, item.Racer, item.GroupTeams.Count());
}

結果:

1950: Nino Farina          0 
1952: Alberto Ascari       0 
1953: Alberto Ascari       0 
1951: Juan Manuel Fangio   0 
1954: Juan Manuel Fangio   0 
1955: Juan Manuel Fangio   0 
1956: Juan Manuel Fangio   0 
1957: Juan Manuel Fangio   0 
1958: Mike Hawthorn        1 
1961: Phil Hill            1

2、join…on…equals…支援多個鍵關聯,可以使用匿名型別來對多個鍵值進行Join,如下所示:

// 查詢表示式
var query17 = from r in racers
              join r2 in teams on new { Name = r.Name.Substring(0, 1), Year = r.Year } equals new { Name = r2.Name.Substring(0, 1), Year = r2.Year } into yearResults
              select new
              {
                  Results = yearResults
              };

foreach (var item in query17)
{
    foreach (var info in item.Results)
    {
        Console.WriteLine(info.Name);
    }
}
//McLaren

七、集合操作

集合操作通過呼叫實體類的 GetHashCode() 和 Equals() 方法比較物件。 對於自定義比較,可以傳遞實現 IEqualityComparer介面的物件。

  • 1) Union:並集,返回兩個序列的並集,去掉重複元素。
  • 2) Concat:連線,返回兩個序列的並集。
  • 3) Intersect:交集,返回兩個序列中都有的元素,即交集。
  • 4) Except:差集,返回只出現在一個序列中的元素,即差集。

業務說明:獲取使用車型”Ferrari”和車型”Mclaren”都獲得過車手冠軍車手列表

void Main()
{
    Func<string, IEnumerable<Racer>> racersByCar = car => from r in Formula1.GetChampions()
                                                          from c in r.Cars
                                                          where c == car
                                                          orderby r.LastName
                                                          select r;

    foreach (var racer in racersByCar("Ferrari").Intersect(racersByCar("McLaren"), new RacerComparer()))
    {
        Console.WriteLine(racer);
    }
}


public class RacerComparer : IEqualityComparer<Racer>
{
    public bool Equals(Racer x, Racer y)
    {
        if (Object.ReferenceEquals(x, y)) return true;
        return x != null && y != null && x.FirstName == y.FirstName && x.LastName == y.LastName;
    }

    public int GetHashCode(Racer obj)
    {
        int hashStudentId = obj.FirstName.GetHashCode();
        int hashScore = obj.LastName.GetHashCode();
        return hashStudentId ^ hashScore;
    }
}

結果:

Niki Lauda

  • 5) Zip:通過使用指定的委託函數合併兩個序列,集合的總個數不變。

範例:合併html開始標籤和結束標籤

var letters = new string[] { "A", "B", "C", "D", "E" };
var numbers = new int[] { 1, 2, 3 };
var q = letters.Zip(numbers, (l, n) => l + n.ToString());
foreach (var s in q)
    Console.WriteLine(s);

結果:

A1 
B2 
C3

  • 6) SequenceEqual:判斷兩個序列是否相等,需要內容及順序都相等。

範例:

int[] arr1 = { 1, 4, 7, 9 };
int[] arr2 = { 1, 7, 9, 4 };
Console.WriteLine("排序前 是否相等:{0}"
    , arr1.SequenceEqual(arr2) ? "是" : "否");  // 否
Console.WriteLine();
Console.WriteLine("排序後 是否相等:{0}"
    , arr1.SequenceEqual(arr2.OrderBy(k => k)) ? "是" : "否"); // 是

八、分割區操作符

擴充套件方法 Take() 和 Skip() 等的分割區操作可以用於分頁。

新增在查詢的“最後”,返回集合的一個子集。

1、Take():

從序列的開頭返回指定數量的連續元素。

2、TakeWhile():

只要滿足指定的條件,就會返回序列的元素。

從第一個元素開始, 讀取Starts小於40的人員列表,只要遇到大於40的元素就立即停止返回。

var racers = (from r in Formula1.GetChampions()
              orderby r.Starts
              select r
              )
              <strong>.TakeWhile(p </strong><strong>=> p.Starts < 40</strong><strong>);</strong>

foreach (var name in racers)
{
    Console.WriteLine($"{name:A}");
}

結果:

Alberto Ascari, Italy; starts: 32, wins: 10 
Nino Farina, Italy; starts: 33, wins: 5

3、Skip():

跳過序列中指定數量的元素,然後返回剩餘的元素。

業務說明:將車手冠軍列表按每頁5個名字進行分頁。

int pageSize = 5;

int numberPages = (int)Math.Ceiling(Formula1.GetChampions().Count() / (double)pageSize);

for (int page = 0; page < numberPages; page++)
{
    Console.WriteLine("Page {0}", page);

    var racers = (
                  from r in Formula1.GetChampions()
                  orderby r.LastName
                  select r.FirstName + " " + r.LastName
                  )
                  .Skip(page * pageSize).Take(pageSize);

    foreach (var name in racers)
    {
        Console.WriteLine(name);
    }
}

結果:

Page 0 
Fernando Alonso 
Mario Andretti 
Alberto Ascari 
Jack Brabham 
Jim Clark 
Page 1 
Juan Manuel Fangio 
Nino Farina 
Emerson Fittipaldi 
Mika Hakkinen 
Mike Hawthorn

4、SkipWhile():

只要滿足指定的條件,就跳過序列中的元素,然後返回剩餘元素。

九、聚合操作符

聚合操作符返回一個值。     

1、LongCount:返回一個 System.Int64,表示序列中的元素的總數量。

業務說明:下面的Count 方法只返回獲得冠軍次數超過三次的賽車手,因為同一個查詢中需要使用同一個計數超過一次,所以使用let 子句定義了一個變數 numberYear.

var query = from r in Formula1.GetChampions()
            let numberYears = r.Years.Count()
            where numberYears >= 3
            orderby numberYears descending, r.LastName
            select new
            {
                Name = r.FirstName + " " + r.LastName,
                TimesChampion = numberYears
            };

foreach (var r in query)
{
    Console.WriteLine("{0} {1}", r.Name, r.TimesChampion);
}
//Michael Schumacher 7
//Juan Manuel Fangio 5
//Alain Prost 4
//Jack Brabham 3
//Niki Lauda 3
//Nelson Piquet 3
//Ayrton Senna 3
//Jackie Stewart 3

2、Sum: 序列中的所有數位的和。

業務說明:下面的Sum 方法用於計算一個國家贏得比賽的總次數。

首先根據國家對賽車手分組,再在新建立的匿名型別中,把Wins 屬性賦予某個國家贏得比賽的總次數。

var countries = (from c in
                     from r in Formula1.GetChampions()
                     group r by r.Country into c
                     select new
                     {
                         Country = c.Key,
                         Wins = (from r1 in c
                                 select r1.Wins).Sum()
                     }
                 orderby c.Wins descending, c.Country
                 select c).Take(5);

foreach (var country in countries)
{
    Console.WriteLine("{0} {1}", country.Country, country.Wins);
}
//UK 138
//Germany 91
//Brazil 78
//France 51
//Finland 40

3、Aggregate: 傳遞一個 lambda 表示式,該表示式對所有的值進行聚合。

業務說明:Aggregate的 
第一個引數是演演算法的種子,即初始值。(可選) 
第二個引數是一個表示式,用來對每個元素進行計算(委託第一個引數是累加變數,第二個引數當前項)。 
第三個引數是一個表示式,用來對最終結果進行資料轉換

int[] numbers = { 1, 2, 3 };
int y = numbers.Aggregate((tol, n) => prod + n); // 1+2+3 = 6
int x = numbers.Aggregate(0, (tol, n) => tol + n); // 0+1+2+3 = 6
int z = numbers.Aggregate(0, (tol, n) => tol + n, r => r * 2);// (0+1+2+3)*2 = 12

十、轉換操作符

查詢可以推遲到存取資料項時再執行。在迭代中使用查詢時,查詢會執行。

而使用轉換操作符會立即執行查詢,把查詢結果放在陣列、列表或字典中。

LINQ本身支援四種不同的集合生成方式,包含生成陣列的ToArray()、生成列表的ToList、生成字典集合的ToDictionary 以及生成Lookup<tkey,telement>類的ToLookup

1) Cast:

將非泛型的 IEnumerable 集合元素轉換為指定的泛型型別,若型別轉換失敗則丟擲異常。 
如果需要在非型別化的集合上(如ArrayList)使用LINQ 查詢,就可以使用Cast 方法。

在下面的例子中,基於Object型別的ArrayList集合用Racer物件填充。

var list = new ArrayList(Formula1.GetChampions() as System.Collections.ICollection);

var query = from r in list.Cast()
            where r.Country == "USA"
            orderby r.Wins descending
            select r;
foreach (var racer in query)
{
    Console.WriteLine("{0:A}", racer);
}
//Mario Andretti, USA; starts: 128, wins: 12
//Phil Hill, USA; starts: 48, wins: 3

2) ToArray:

從 IEnumerable 建立一個陣列。

3) ToList:

立即執行查詢,從 IEnumerable 建立一個 List。

4) ToDictionary:

根據指定的鍵選擇器函數,從 IEnumerable 建立一個 Dictionary<tkey,tvalue>。

將列表轉換為字典:

var spartans = new List<dynamic>
        {
            new {Opponent="UAB",Score="55-18"},
            new {Opponent="Bowling Green",Score="55-18"},
            new {Opponent="Pittsburgh",Score="55-18"},
            new {Opponent="Notre Dame",Score="55-18"}
        };
//字典是一種鍵值對的集合,ToDictionary 將一個IEnumerable<T>物件(比如LINQ查詢所返回的結果)
//轉換為一個IDictionary<Key,Value>物件。
IDictionary<string, dynamic> stats = spartans.ToDictionary(key => (string)key.Opponent);
Console.WriteLine("Spartans vs. {0} {1}", stats["Notre Dame"].Opponent, stats["Notre Dame"].Score);

5) ToLookup:

根據指定的鍵選擇器函數,從 IEnumerable 建立一個 System.Linq.Lookup。 
ToLookup使用比較複雜,Lookup類似於Dictionary,不過,Dictionary每個鍵只對應一個值,而Lookup則是1:n 的對映。 
Lookup沒有公共建構函式,而且是不可變的。在建立Lookup之後,不能新增或刪除其中的元素或鍵。(可以將ToLookup 視為GroupBy與ToDictionary的功能合體) 
業務說明:將車手冠軍按其使用車型進行分組,並顯示使用”williams”車型的車手名字。

ILookup<string, Racer> racers =
    (from r in Formula1.GetChampions()
     from c in r.Cars //使用複合的from 查詢
     select new
     {
         Car = c,
         Racer = r
     }
     ).ToLookup(cr => cr.Car, cr => cr.Racer);

if (racers.Contains("Williams"))
{
    foreach (var williamsRacer in racers["Williams"])
    {
        Console.WriteLine(williamsRacer);
    }
}
//Alan Jones
//Keke Rosberg
//Nelson Piquet
//Nigel Mansell
//Alain Prost
//Damon Hill
//Jacques Villeneuve

6) DefaultIfEmpty:

返回指定序列的元素;如果序列為空,則返回包含型別引數的預設值的單一元素集合 。   

var defaultArrCount = (new int[0]).DefaultIfEmpty().Count();
Console.WriteLine(defaultArrCount);
//1

7) AsEnumerable:

返回型別為 IEnumerable 。用於處理LINQ to Entities操作遠端資料來源與本地集合的共同作業

十一、生成操作符

生成操作符返回一個新的集合。三個生成操作符不是擴充套件方法,而是返回序列的正常靜態方法。

1) Empty:

生成一個具有指定型別引數的空序列 IEnumerable。 
Empty() 方法返回一個不返回值的迭代器,用於需要一個集合的引數,可以給引數傳遞空集合。

string[] names1 = { "Hartono, Tommy" };
string[] names2 = { "Adams, Terry", "Andersen, Henriette Thaulow", "Hedlund, Magnus", "Ito, Shu" };
string[] names3 = { "Solanki, Ajay", "Hoeing, Helge", "Andersen, Henriette Thaulow", "Potra, Cristina", "Iallo, Lucio" };

List<string[]> namesList = new List<string[]> { names1, names2, names3 };

IEnumerable<string> allNames =  namesList.Aggregate(Enumerable.Empty<string>(), (current, next) => next.Length > 3 ? current.Union(next) : current);

foreach (string name in allNames)
{
    Console.WriteLine(name);
}
//Adams, Terry
//Andersen, Henriette Thaulow
//Hedlund, Magnus
//Ito, Shu
//Solanki, Ajay
//Hoeing, Helge
//Potra, Cristina
//Iallo, Lucio

2) Range:

生成指定範圍內的整數的序列 IEnumerable。 
如需要填充一二範圍的數位,此時就應使用 Range() 方法。這個方法第一個引數作為起始值,把第二個引數作為要填充的項數

var values = Enumerable.Range(1, 20);

foreach (var value in values)
{
    Console.WriteLine(value);
}
// 結果 1 2 3 4 5 6 ......  19 20

Range() 方法不返回填充所定義值的集合,與其他方法一樣,推遲查詢,返回一個 RangeEnumerator。其中用 yield return 語句,來遞增值。該結果也可以與其他擴充套件方法一起用。

var values = Enumerable.Range(1, 5).Select(n => n * 3);

foreach (var value in values)
{
     Console.WriteLine(value);
}
// 3 6 9 12 15

3) Repeat:

生成包含一個重複值的序列 IEnumerable。 
Repeat() 方法 返回一個迭代器,把同一個值重複特定的次數。

IEnumerable<string> strings = Enumerable.Repeat("I like programming.", 3);

foreach (String str in strings)
{
    Console.WriteLine(str);
}
//I like programming.
//I like programming.
//I like programming.

十二、量詞操作符

如果元素序列滿足指定的條件,量詞操作符就返回布林值。

1) Any:

確定序列是否包含任何元素;或確定序列中的任何元素是否都滿足條件。

//獲取是否存在姓為「Schumacher」的車手冠軍
var hasRacer_Schumacher = Formula1.GetChampions().Any(r => r.LastName == "Schumacher");
Console.WriteLine(hasRacer_Schumacher);
//True

2) All:

確定序列中的所有元素是否滿足條件。

3) Contains:

確定序列是否包含指定的元素。

十三、元素操作符

這些元素操作符僅返回一個元素,不是IEnumerable。(預設值:值型別預設為0,參照型別預設為null)

業務說明:獲取冠軍數排名第三的車手冠軍

var Racer3 = Formula1.GetChampions()
    .OrderByDescending(r => r.Wins)
    .ElementAtOrDefault(2);

Console.WriteLine(Racer3);
//Ayrton Senna
  • 1) First:返回序列中的第一個元素;如果是空序列,此方法將引發異常。
  • 2) FirstOrDefault:返回序列中的第一個元素;如果是空序列,則返回預設值default(TSource)。
  • 3) Last:返回序列的最後一個元素;如果是空序列,此方法將引發異常。
  • 4) LastOrDefault:返回序列中的最後一個元素;如果是空序列,則返回預設值default(TSource)。
  • 5) Single:返回序列的唯一元素;如果是空序列或序列包含多個元素,此方法將引發異常。
  • 6) SingleOrDefault:返回序列中的唯一元素;如果是空序列,則返回預設值default(TSource);如果該序列包含多個元素,此方法將引發異常。
  • 7) ElementAt:返回序列中指定索引處的元素,索引從0開始;如果索引超出範圍,此方法將引發異常。
  • 8) ElementAtOrDefault:返回序列中指定索引處的元素,索引從0開始;如果索引超出範圍,則返回預設值default(TSource)。

十四、並行查詢,並行Linq

AsParallel() 方法,擴充套件 IEnumerable 介面,返回 ParallelQuery類,所以正常的集合類可以以平行方式查詢。

var query24 = from r in Formual.GetChampions().AsParallel() select r;

十五、分割區器

AsParallel()方法不僅擴充套件了 IEnumerable 介面,還擴充套件了 Partitioner 類。通過它,可以影響建立的分割區。

手動建立一個分割區器

var query25 = from r in Partitioner.Create (Formual.GetChampions(), true).AsParallel() select r;

十六、取消

.NET 提供一個標準方法,來取消長時間執行的任務,也適用於並行Linq。

要取消長時間執行的查詢可以給查詢新增WithCancellation() 方法,並傳遞一個 CancellactionToken令牌作為引數。

CancelllationToken令牌從CancellactionTokenSource類中建立。該查詢在單獨的執行緒中執行,在該執行緒中,捕獲一個OperationCanceledException型別的異常。如果取消了查詢就觸發這個異常。

在主執行緒中,呼叫CancellationTokenSource類的Cancel()方法可以取消任務。

CancellationTokenSource cts = new CancellationTokenSource();

Task.Factory.StartNew(() =>
{
    try
    {
        var res = from r in Formual.GetChampions().AsParallel().WithCancellation(cts.Token) select r;
        Console.WriteLine("query finished, sum:{0}", res);
    }
    catch (OperationCanceledException ex)
    {
        Console.WriteLine("canceled!");
        Console.WriteLine(ex.Message);
    }
});

string input = Console.ReadLine();
if (input.ToLower().Equals("y"))
{
    cts.Cancel();
    Console.WriteLine("canceled 2!");
}

到此這篇關於C#使用LINQ查詢操作符的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com