一篇文章教你學會ASP.Net Core LINQ基本操作

2022-09-03 21:01:35

一篇文章教你學會ASP.Net Core LINQ基本操作

為什麼要使用LINQ

LINQ中提供了很多集合的擴充套件方法,配合lambda能簡化資料處理。

例如我們想要找出一個IEnumerable<int>中所有大於10的元素,使用LINQ則可以這樣寫

static void Main(string[] args)
{
    int[] nums = new int[] { 3, 5, 6, 5, 10, 12, 14, 7 };
    IEnumerable<int> res = nums.Where(a => a > 10);
    foreach (int i in res)
        Console.WriteLine(i);
}

其中使用IEnumerableusing System.Collections.Generic;

使用Where方法要using System.Linq;,該方法會遍歷每個元素然後去判斷是否大於10

LINQ背後原理

為了解LINQ背後的原理,我們首先去實現一個簡單的Where方法

第一種方案:

static IEnumerable<int> MyWhere1(IEnumerable<int> items, Func<int, bool> f)
{
    List<int> res = new List<int>();
    foreach (int i in items)
        if (f(i) == true)
            res.Add(i);
    return res;
}

第二種方案:

static IEnumerable<int> MyWhere2(IEnumerable<int> items, Func<int, bool> f)
{
    List<int> res = new List<int>();
    foreach (int i in items)
        if (f(i) == true)
            yield return i;
}

那麼這兩種方案的區別是什麼?第一種方案是把所有元素全部檢查一遍,把符合要求的元素放到List<int> res裡面,然後返回res;然而第二種方案使用yield,是一種「流水線」方式處理,找到符合條件的元素立即返回,返回後Console.WriteLine立即能夠列印,從而提高了資料處理效率。

LINQ的常用擴充套件方法

LINQ提供了很多擴充套件方法,大部分都在System.Linq名稱空間中。

接下來準備一些資料,用於下面的操作。

首先定義一個員工類,裡面有姓名工資等成員。

class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public bool Gender { get; set; }
    public int Salary { get; set; }
    public override string ToString()
    {
        return $"ID={Id}, Name={Name}, Age={Age}, Gender={Gender}, Salary={Salary}";
    }

}

然後再Main方法中建立範例匯入資料,並將所有範例儲存到列表中。

List<Employee> lst = new List<Employee>();
lst.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
lst.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
lst.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
lst.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
lst.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
lst.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
lst.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
lst.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 8000 });

Where方法

該方法會遍歷每個元素然後去判斷是否符合條件,符合條件的元素則被返回。

IEnumerable<Employee> res = lst.Where(e => e.Age > 20); //把年齡大於20的返回過來

foreach (Employee e in res)
    Console.WriteLine(e);

返回結果

ID=1, Name=jerry, Age=28, Gender=True, Salary=5000
ID=2, Name=jim, Age=33, Gender=True, Salary=3000
ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000
ID=6, Name=nancy, Age=35, Gender=False, Salary=8000
ID=7, Name=zack, Age=35, Gender=True, Salary=8500
ID=8, Name=jack, Age=33, Gender=True, Salary=8000

Count方法

該方法會返回符合條件的元素的個數

Console.WriteLine(lst.Count(e=>e.Salary>8000));

返回結果

2

Any方法

該方法會判斷是否存在至少一個元素符合條件。另外,如果傳入的引數為空,則會判斷IEnumerable(或者實現了IEnumerable介面的其他類,如List)是否存在元素。

如以下程式碼

List<int> test = new List<int>();
Console.WriteLine(test.Any());

由於列表為空,test.Any()返回的就是false

如果判斷是否存在Employee型別的元素於lst中,則程式碼如下,其返回值為true

Console.WriteLine(lst.Any(e=>e.Salary>8000));

同樣的,我們使用Count方法也可以達成此目的(判斷返回元素個數是否為0),但是Count方法相對於Any方法效率較低

這是因為Any找到一個符合條件的元素會立即返回,而Count方法是統計個數,找到一個符合元素後還要繼續向後找。

有關一條資料的方法

有關一條資料的方法有好幾種,不過在細節上略有不一樣,所以我們把他們放一塊介紹。

方法 描述
Single() 有且只有一條滿足要求的資料
SingleOrDefault() 最多隻有一條滿足要求的資料
First() 至少有一條,並且返回第一條
FirstOrDefault() 返回第一條或預設值

Signle方法

Employee elem = lst.Single(e => e.Salary > 8000);  //錯誤,有多條資料滿足條件
Employee elem = lst.Single(e => e.Salary > 8500);	//正確,僅一條資料滿足條件

SingleOrDefault方法

對於SingleOrDefault,當且僅當存在一條資料滿足條件,返回該資料;如果存在多條則報錯;如果不存在則返回預設值。

Employee elem = lst.SingleOrDefault(e=>e.Salary>8000);  //錯誤,有多條資料滿足條件
Employee elem = lst.SingleOrDefault(e=>e.Salary>8500);	//正確,僅一條資料滿足條件

下面我們來看下預設值的情況

int[] nums = new int[] { 1, 2, 3 };
int i = nums.SingleOrDefault(i => i > 10);
Console.WriteLine(i);

由於不存在大於10的整型數位,所以該方法返回變數i的預設值,輸出結果為0

First方法

該方法要求資料至少有一條滿足條件,並且返回查詢到的第一條資料。

Employee test = lst.First(e=>e.Salary>9000);	//報錯,不存在資料滿足條件
//正確,滿足年齡大於16的有多條,僅按照我們新增資料的順序返回第一條
Employee test = lst.First(e=>e.Age>16);
Console.WriteLine(test);

輸出結果

ID=1, Name=jerry, Age=28, Gender=True, Salary=5000

FirstOrDefault方法

該方法返回符合條件的第一條資料,否則返回預設值

如下方程式碼,從陣列中返回一個大於2的整數,其輸出結果為3

int[] nums = new int[] { 1, 2, 3, 4, 5, 6 };
int i = nums.FirstOrDefault(e => e > 2);
Console.WriteLine(i);

我們再來看看返回預設值的情況

int[] nums = new int[] { 1, 2, 3, 4, 5, 6 };
int i = nums.FirstOrDefault(e => e > 10);
Console.WriteLine(i);

由於陣列中不存在大於10的數,所以i的值就是其預設值0

排序方法

一般排序

排序方法有兩種

方法 描述
OrderBy() 正序排序
OrderByDescending() 逆序排序

二者用法幾乎一致,此處僅演示OrderBy方法

IEnumerable<Employee> res2 = lst.OrderBy(e => e.Age);
foreach (Employee e in res2)
    Console.WriteLine(e);

其輸出結果為

ID=4, Name=lucy, Age=16, Gender=False, Salary=2000
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000
ID=1, Name=jerry, Age=28, Gender=True, Salary=5000
ID=2, Name=jim, Age=33, Gender=True, Salary=3000
ID=8, Name=jack, Age=33, Gender=True, Salary=8000
ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=6, Name=nancy, Age=35, Gender=False, Salary=8000
ID=7, Name=zack, Age=35, Gender=True, Salary=8500

很顯然資料已經按照年齡從小至大的順序進行排序了。

此外應該注意的是,該方法必須有引數。如果想要對一個陣列進行排序,正確寫法如下

int[] nums2 = new int[] { 3, 1, 2, 4, 5, 6 };
IEnumerable<int> resNum = nums2.OrderBy(i => i);
//寫成nums2.OrderBy()是錯誤的

多排序

所謂多排序,就是按照一個條件對資料進行排序後,存在多個資料該條件下的值一致,然後再對這些值一致的資料按照其他條件排序。

其方法也有兩個,一般是在OrderBy()OrderByDescending()之後呼叫

方法 描述
ThenBy() 正序再排序
ThenByDescending() 逆序再排序

我們對文章上方的資料首先按照對年齡進行排序,然後對年齡一致的員工再按照工資進行逆排序,編寫程式碼如下:

IEnumerable<Employee> sortTest = lst.OrderBy(x => x.Age).ThenByDescending(x => x.Salary);
foreach (Employee emp in sortTest)
    Console.WriteLine(emp);

輸出如下:

ID=4, Name=lucy, Age=16, Gender=False, Salary=2000
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000
ID=1, Name=jerry, Age=28, Gender=True, Salary=5000
ID=8, Name=jack, Age=33, Gender=True, Salary=8000
ID=2, Name=jim, Age=33, Gender=True, Salary=3000
ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=7, Name=zack, Age=35, Gender=True, Salary=8500
ID=6, Name=nancy, Age=35, Gender=False, Salary=8000

限制結果集的方法

限制結果集,獲取部分資料的方法一般是利用SkipTake方法

例如我想要從上述員工資料中,獲取從第2條開始連續的3條資料,則程式碼可以這樣寫;

IEnumerable<Employee> sortTest = lst.Skip(2).Take(3);
foreach (Employee emp in sortTest)
    Console.WriteLine(emp);

其輸出結果如下:

ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=4, Name=lucy, Age=16, Gender=False, Salary=2000
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000

當然,Skip方法和Take方法也可以單獨使用:

Take單獨使用

IEnumerable<Employee> sortTest = lst.Take(3);
foreach (Employee emp in sortTest)
    Console.WriteLine(emp);

輸出:

ID=1, Name=jerry, Age=28, Gender=True, Salary=5000
ID=2, Name=jim, Age=33, Gender=True, Salary=3000
ID=3, Name=lily, Age=35, Gender=False, Salary=9000

Skip單獨使用

IEnumerable<Employee> sortTest = lst.Skip(2);
foreach (Employee emp in sortTest)
    Console.WriteLine(emp);

輸出:

ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=4, Name=lucy, Age=16, Gender=False, Salary=2000
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000
ID=6, Name=nancy, Age=35, Gender=False, Salary=8000
ID=7, Name=zack, Age=35, Gender=True, Salary=8500
ID=8, Name=jack, Age=33, Gender=True, Salary=8000

聚合函數(方法)

LINQ聚合函數常用的有這些,但應當注意的是它們的返回值型別不與其他LINQ的方法一樣是IEnumerable而是條件的值的型別

方法 描述
Max() 返回給定條件的最大值
Min() 返回給定條件的最小值
Average() 返回給定條件的平均值
Sum() 返回給定條件的和
Count() 統計滿足條件的資料的個數

這些方法用法大致相同,甚至Count方法在上文中已經介紹過,此處僅用Max方法演示

int maxSalary = lst.Max(x => x.Salary);
Console.WriteLine(maxSalary);

該樣例會輸出所有員工的最大工資(請注意maxSalary的型別)

如果想要找到大於30歲的員工的最高工資,則可以

int maxSalary = lst.Where(x=>x.Age > 30).Max(x => x.Salary);

GroupBy方法

該方法用於對資料分組,其引數是分組條件表示式,返回值為IGrouping<TKey, TSource>型別的泛型IEnumerable。

我們編寫程式碼來實現很具年齡分組:

IEnumerable<IGrouping<int, Employee>> items = lst.GroupBy(x => x.Age);
foreach (var item in items)
{
    Console.WriteLine($"年齡為{item.Key}的分組成員有:");
    foreach (var i in item)
        Console.WriteLine(i);
    Console.WriteLine();
}

其輸出結果為:

年齡為28的分組成員有:
ID=1, Name=jerry, Age=28, Gender=True, Salary=5000

年齡為33的分組成員有:
ID=2, Name=jim, Age=33, Gender=True, Salary=3000
ID=8, Name=jack, Age=33, Gender=True, Salary=8000

年齡為35的分組成員有:
ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=6, Name=nancy, Age=35, Gender=False, Salary=8000
ID=7, Name=zack, Age=35, Gender=True, Salary=8500

年齡為16的分組成員有:
ID=4, Name=lucy, Age=16, Gender=False, Salary=2000

年齡為25的分組成員有:
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000

通過列印我們可以發現IEnumerable元素為IGrouping型別,其鍵與值對應關係是一對多的。在這裡每個元素的鍵就是年齡,而值為具有相同年齡的Employee型別的員工資料。

投影與匿名型別

投影是把集合中每一項轉化為另外一種型別。

IEnumerable<string> items = lst.Where(x => x.Salary > 5000).Select(x => x.Gender ? "男" : "女");
foreach (var item in items)
    Console.WriteLine(item);

輸出結果為:

女
女
男
男

匿名型別沒有名稱,所以我們沒有辦法去用型別名去宣告它,而是需要用到var關鍵字

var items = lst.Select(e => new
{
    XingMing = e.Name,
    NianLing = e.Age,
    Xingbie = e.Gender ? "男" : "女"
});
foreach (var item in items)
    Console.WriteLine(item);

其輸出結果為:

{ XingMing = jerry, NianLing = 28, Xingbie = 男 }
{ XingMing = jim, NianLing = 33, Xingbie = 男 }
{ XingMing = lily, NianLing = 35, Xingbie = 女 }
{ XingMing = lucy, NianLing = 16, Xingbie = 女 }
{ XingMing = kimi, NianLing = 25, Xingbie = 男 }
{ XingMing = nancy, NianLing = 35, Xingbie = 女 }
{ XingMing = zack, NianLing = 35, Xingbie = 男 }
{ XingMing = jack, NianLing = 33, Xingbie = 男 }

型別轉換

在實際使用中,我們往往不是一定用IEnumerable,還有可能是List等,所以需要用到型別轉換

例如我們利用Where方法返回工資大於6000的員工存放到IEnumerable中,然後將其轉化為List型別

List<Employee> lst2 = lst.Where(e => e.Salary > 6000).ToList();

此外還有ToArray等方法,此處不過多說明。

鏈式呼叫

所謂鏈式呼叫就是呼叫完一個函數(方法)後還能再後面繼續跟著呼叫其它函數(方法)。

由於LINQ絕大多數方法返回的都是IEnumerable方法,而且絕大部分都是針對IEnumerable介面,所以可以在呼叫方法後繼續呼叫其他方法。

lst.Where(e => e.Salary > 6000).ToList();

例如我們定義陣列

int[] nums = new int[] { 1, 2, 3, 4, 5, 6 };

在小於3的元素中選取最大值

int a = nums.Where(x=>x<3).Max();

對於上面這行nums.Where(x=>x<3).Max()Where方法後加一個點然後再呼叫Max方法的形式就叫做鏈式呼叫。

查詢語法

對於上述的使用WhereSelect等擴充套件方法進行資料查詢的寫法叫做LINQ方法語法

然而還有一種叫做查詢語法

我們同樣定義一個陣列演示

int[] nums = new int[] { 6,5,4,3,2,1 };

我們取小於3的元素,然後進行正序排序,則用查詢語法則可以如下:

var items = from e in nums
            where e < 3
            orderby e
            select e;

這裡需要注意,查詢語法需要以select或group子句結尾

那麼問題來了,方法語法與查詢語法有什麼區別?我們可以用方法語法寫一段相同效果的語法,然後用反編譯器(ILSpy)去看一下程式碼。

其反編譯結果給出了查詢語法的形式,然後對查詢語法生成檔案進行反編譯,發現結果相同,這說明兩種方法在編譯後沒有任何區別只是寫法不同

結束

LINQ的基本操作大致就這些,感謝楊中科老師提供的課程