1. 程式人生 > >.NET中那些所謂的新語法之四:標準查詢運算子與LINQ

.NET中那些所謂的新語法之四:標準查詢運算子與LINQ

開篇:在上一篇中,我們瞭解了預定義委託與Lambda表示式等所謂的新語法,這一篇我們繼續征程,看看標準查詢運算子和LINQ。標準查詢運算子是定義在System.Linq.Enumerable類中的50多個為IEnumerable<T>準備的擴充套件方法,而LINQ則是一種類似於SQL風格的查詢表示式,它們可以大大方便我們的日常開發工作。因此,需要我們予以關注起來!

/* 新語法索引 */

一、擴充套件方法哪家強?標準查詢運算子:[ C# 3.0/.NET 3.x 新增特性 ]

  標準查詢運算子提供了包括篩選、投影、聚合、排序等功能在內的查詢功能,其本質是定義在System.Linq.Enumerable

類中的50多個為IEnumerable<T>準備的擴充套件方法

  從上圖可以看出,在Enumerable類中提供了很多的擴充套件方法,這裡我們選擇其中幾個最常用的方法來作一點介紹,使我們能更好地利用它們。首先,我們需要一點資料來進行演示:

    public class Person
    {
        public int ID { get; set; }

        public string Name { get; set; }

        public int Age { get; set; }

        public
bool Gender { get; set; } public override string ToString() { return string.Format("{0}-{1}-{2}-{3}", ID, Name, Age, Gender == true ? "" : ""); } } public class LitePerson { public string Name { get; set; }
public override string ToString() { return Name; } } public class Children { public int ChildID { get; set; } public int ParentID { get; set; } public string ChildName { get; set; } public override string ToString() { return string.Format("{0}-{1}-{2}", ChildID, ChildName, ParentID); } } static List<Person> GetPersonList() { List<Person> personList = new List<Person>() { new Person(){ID=1,Name="Edison Chou",Age=25,Gender=true}, new Person(){ID=2,Name="Edwin Chan",Age=20,Gender=true}, new Person(){ID=3,Name="Jackie Chan",Age=40,Gender=true}, new Person(){ID=4,Name="Andy Lau",Age=55,Gender=true}, new Person(){ID=5,Name="Kelly Chan",Age=45,Gender=false} }; return personList; } static List<Children> GetChildrenList() { List<Children> childrenList = new List<Children>() { new Children(){ChildID=1,ParentID=1,ChildName="Lucas"}, new Children(){ChildID=2,ParentID=1,ChildName="Louise"}, new Children(){ChildID=3,ParentID=3,ChildName="Edward"}, new Children(){ChildID=4,ParentID=4,ChildName="Kevin"}, new Children(){ChildID=5,ParentID=5,ChildName="Mike"} }; return childrenList; } static List<Person> GetMorePersonList() { List<Person> personList = new List<Person>() { new Person(){ID=1,Name="愛迪生",Age=100,Gender=true}, new Person(){ID=2,Name="瓦特",Age=120,Gender=true}, new Person(){ID=3,Name="牛頓",Age =150,Gender=true}, new Person(){ID=4,Name="圖靈",Age=145,Gender=true}, new Person(){ID=5,Name="夏農",Age=120,Gender=true}, new Person(){ID=6,Name="居里夫人",Age=115,Gender=false}, new Person(){ID=6,Name="居里夫人2",Age=115,Gender=false}, new Person(){ID=7,Name="居里夫人3",Age=115,Gender=false}, new Person(){ID=8,Name="居里夫人4",Age=115,Gender=false}, new Person(){ID=9,Name="居里夫人5",Age=115,Gender=false}, new Person(){ID=10,Name="居里夫人6",Age=115,Gender=false}, new Person(){ID=11,Name="居里夫人7",Age=115,Gender=false}, new Person(){ID=12,Name="居里夫人8",Age=115,Gender=false}, new Person(){ID=13,Name="居里夫人9",Age=115,Gender=false}, new Person(){ID=14,Name="居里夫人10",Age=115,Gender=false}, new Person(){ID=15,Name="居里夫人11",Age=115,Gender=false}, new Person(){ID=16,Name="居里夫人12",Age=115,Gender=false}, new Person(){ID=17,Name="居里夫人13",Age=115,Gender=false}, new Person(){ID=18,Name="居里夫人14",Age=115,Gender=false} }; return personList; }
View Code

1.1 篩選高手Where方法

  Where方法提供了我們對於一個集合的篩選功能,但需要提供一個帶bool返回值的“篩選器”(匿名方法、委託、Lambda表示式均可),從而表明集合中某個元素是否應該被返回。這裡,我們以上面的資料為例,篩選出集合中所有性別為男,年齡大於20歲的子集合,藉助Where方法實現如下:

        static void SQOWhereDemo()
        {
            List<Person> personList = GetPersonList();

            List<Person> maleList = personList.Where(p =>
                p.Gender == true && p.Age > 20).ToList();
            maleList.ForEach(m => Console.WriteLine(m.ToString()));
        }

  (1)執行結果如下圖所示:

  (2)由本系列文章的第二篇可知,擴充套件方法的本質是在執行時呼叫擴充套件類的靜態方法,而我們寫的Lambda表示式在編譯時又會被轉為匿名方法(準確地說應該是預定義泛型委託例項)作為方法引數傳入擴充套件方法中,最後呼叫執行該擴充套件方法生成一個新的List集合返回。

1.2 投影大牛Select方法

  Select方法可以查詢投射,返回新物件集合。這裡,假設我們先篩選出所有男性集合,再根據男性集合中所有項的姓名生成子集合(這是一個不同於原型別的型別),就可以藉助Select方法來實現。

        static void SQOSelectDemo()
        {
            List<Person> personList = GetPersonList();

            List<LitePerson> liteList = personList.Where(p =>
                p.Gender == true).Select(
                p => new LitePerson() { Name = p.Name }).ToList();
            liteList.ForEach(p => Console.WriteLine(p.ToString()));
        }

  (1)執行結果如下圖所示:

  (2)這裡也可以採用匿名類,可以省去事先宣告LitePerson類的步湊,但需要配合var使用:

var annoyList = personList.Where(p =>
    p.Gender == true).Select(
    p => new { Name = p.Name }).ToList();

  (3)這裡因為實現LitePerson類重寫了ToString()方法,所以這裡直接呼叫了ToString()方法。

1.3 排序小生OrderBy方法

  說到排序,我們馬上想起了SQL中的order by語句,而標準查詢運算子中也為我們提供了OrderBy這個方法,值得一提的就是我們可以進行多條件的排序,因為OrderBy方法返回的仍然是一個IEnumerable<T>的型別,仍然可以繼續使用擴充套件方法。但要注意的是,第二次應該使用ThenBy方法。

        static void SQOOrderByDemo()
        {
            List<Person> personList = GetPersonList();
            // 單條件升序排序
            Console.WriteLine("Order by Age ascending:");
            List<Person> orderedList = personList.OrderBy(p => p.Age).ToList();
            orderedList.ForEach(p => Console.WriteLine(p.ToString()));
            // 單條件降序排序
            Console.WriteLine("Order by Age descending:");
            orderedList = personList.OrderByDescending(p => p.Age).ToList();
            orderedList.ForEach(p => Console.WriteLine(p.ToString()));
            // 多條件綜合排序
            Console.WriteLine("Order by Age ascending and ID descending:");
            orderedList = personList.OrderBy(p => p.Age)
                .ThenByDescending(p => p.ID).ToList();
            orderedList.ForEach(p => Console.WriteLine(p.ToString()));
        }

  執行結果如下圖所示:

1.4 連線道士Join方法

  在資料庫中,我們對兩個表或多個表進行連線查詢時往往會用到join語句,然後指定兩個表之間的關聯關係(例如: a.bid = b.aid)。在標準查詢運算子中,細心的.NET基類庫也為我們提供了Join方法。現在,假設我們有兩個類:Person和Children,其中每個Children物件都有一個ParentID,對應Person物件的ID,現需要打印出所有Person和Children的資訊,可以藉助Join方法來實現。

        static void SQOJoinDemo()
        {
            List<Person> personList = GetPersonList();
            List<Children> childrenList = GetChildrenList();

            // 連線查詢
            var joinedList = personList.Join(childrenList,
                p => p.ID, c => c.ParentID, (p, c) => new
                {
                    ParentID = p.ID,
                    ChildID = c.ChildID,
                    ParentName = p.Name,
                    ChildName = c.ChildName
                }).ToList();
            joinedList.ForEach(c => Console.WriteLine(c.ToString()));
        }

  執行結果如下圖所示:

1.5 分組老師GroupBy方法

  在資料庫中,我們要對查詢結果進行分組會用到 group by 語句,在標準查詢運算子中,我們也有對應的GroupBy方法。這裡,假設我們對Person資料集按照性別進行分類,該怎麼來寫程式碼呢?

        static void SQOGroupByDemo()
        {
            List<Person> personList = GetPersonList();

            IEnumerable<IGrouping<bool, Person>> groups =
                personList.GroupBy(p => p.Gender);
            IList<IGrouping<bool, Person>> groupList = groups.ToList();

            foreach (IGrouping<bool, Person> group in groupList)
            {
                Console.WriteLine("Group:{0}", group.Key ? "" : "");
                foreach (Person p in group)
                {
                    Console.WriteLine(p.ToString());
                }
            }
        }

  (1)這裡需要注意的是:通過GroupBy方法後返回的是一個IEnumerable<IGrouping<TKey, TSource>>型別,其中TKey是分組依據的型別,這裡是根據Gender來分組的,而Gender又是bool型別,所以TKey這裡為bool型別。TSource則是分組之後各個元素的型別,這裡是將List<Person>集合進行分組,因此分完組後每個元素都儲存的是Person型別,所以TSource這裡為Person型別,Do you understand now?

  (2)執行結果如下圖所示:

  (3)可能有人會說我咋記得住GroupBy返回的那個型別,太長了,我也不想記。怎麼辦呢?不怕,我們可以使用var關鍵字嘛:

            var annoyGroups = personList.GroupBy(p => p.Name).ToList();
            foreach (var group in annoyGroups)
            {
                Console.WriteLine("Group:{0}", group.Key);
                foreach (var p in group)
                {
                    Console.WriteLine(p.ToString());
                }
            }

1.6 分頁實戰Skip與Take方法

  相信很多人都使用過標準查詢運算子進行分頁操作,這裡我們再次來看看如何藉助Skip與Take方法來實現分頁操作。還是以PersonList集合為例,假如頁面上的表格每頁顯示5條資料,該怎麼來寫程式碼呢?

        static void SQOPagedDemo()
        {
            // 這裡假設每頁5行資料
            // 第一頁
            Console.WriteLine("First Page:");
            var firstPageData = GetPagedListByIndex(1, 5);
            firstPageData.ForEach(d => Console.WriteLine(d.ToString()));
            // 第二頁
            Console.WriteLine("Second Page:");
            var secondPageData = GetPagedListByIndex(2, 5);
            secondPageData.ForEach(d => Console.WriteLine(d.ToString()));
            // 第三頁
            Console.WriteLine("Third Page:");
            var thirdPageData = GetPagedListByIndex(3, 5);
            thirdPageData.ForEach(d => Console.WriteLine(d.ToString()));
        }

        static List<Person> GetPagedListByIndex(int pageIndex, int pageSize)
        {
            List<Person> dataList = GetMorePersonList();
            return dataList.Skip((pageIndex - 1) * pageSize)
                .Take(pageSize).ToList();
        }

  執行結果如下圖所示:

1.7 淺談延遲載入與即時載入

  (1)延遲載入(Lazy Loading):只有在我們需要資料的時候才去資料庫讀取載入它。

  在標準查詢運算子中,Where方法就是一個典型的延遲載入案例。在實際的開發中,我們往往會使用一些ORM框架例如EF去操作資料庫,Where方法的使用則是每次呼叫都只是在後續生成SQL語句時增加一個查詢條件,EF無法確定本次查詢是否已經新增結束,所以沒有辦法木有辦法在每個Where方法執行的時候確定最終的SQL語句,只能返回一個DbQuery物件,當使用到這個DbQuery物件的時候,才會根據所有條件生成最終的SQL語句去查詢資料庫。

    var searchResult = personList.Where(p =>
          p.Gender == false).Where(p => p.Age > 20)
          .Where(p=>p.Name.Contains("奶茶"));

  (2)即時載入(Eager Loading):載入資料時就把該物件相關聯的其它表的資料一起載入到記憶體物件中去。

  在標準查詢運算子中,FindAll方法就是一個典型的即時載入案例。與延遲載入相對應,在開發中如果使用FindAll方法,EF會根據方法中的條件自動生成SQL語句,然後立即與資料庫進行互動獲取查詢結果,並載入到記憶體中去。

        var searchResult = personList.FindAll(p=>p.Gender == false
                && p.Name.Contains("奶茶"));

二、查詢方式誰更快?LINQ:[ C# 3.0/.NET 3.x 新增特性 ]

2.1 初識LINQ:類似SQL風格的程式碼

  LINQ又稱語言整合查詢,它是C# 3.0的新語法。在更多的人看來,它是一種方便的查詢表示式,或者說是和SQL風格接近的程式碼

    var maleList = from p in personList
                   where p.Gender == true
                   select p;

  (1)LINQ表示式以"from"開始,以"select 或 group by子句"結尾;

  (2)LINQ表示式的輸出是一個 IEnumerable<T> 或 IQueryable<T> 集合;(注:T 的型別 由 select 或 group by 推斷出來)

2.2 LINQ使用:實現除Skip和Take外的標準查詢運算子的功能

  (1)基本條件查詢:

            List<Person> personList = GetPersonList();
            List<Children> childList = GetChildrenList();
            // 基本條件查詢
            Console.WriteLine("Basic Query:");
            var maleList = from p in personList
                           where p.Gender == true
                           select p;
            maleList.ToList().ForEach(m =>
                Console.WriteLine(m.ToString()));
View Code

  (2)排序條件查詢:

            // 排序條件查詢
            Console.WriteLine("Order Query:");
            var orderedList = from p in personList
                              orderby p.Age descending
                              orderby p.Name ascending
                              select p;
            orderedList.ToList().ForEach(m =>
                Console.WriteLine(m.ToString()));
View Code

  (3)連線查詢:

            // Join連線查詢
            Console.WriteLine("Join Query:");
            var joinedList = from p in personList
                             join c in childList
                             on p.ID equals c.ParentID
                             select new
                             {
                                 Person = p,
                                 Child = c
                             };
            foreach (var item in joinedList)
            {
                Console.WriteLine(item.ToString());
            }
View Code

  (4)分組查詢:

            // 分組條件查詢
            Console.WriteLine("Group Query:");
            var groupList = from p in personList
                            group p by p.Gender;
            foreach (var group in groupList)
            {
                Console.WriteLine("Group:{0}", 
                    group.Key? "":"");
                foreach(var item in group)
                {
                    Console.WriteLine(item.ToString());
                }
            }
View Code

  執行結果請參考上一節標準查詢運算子中相關的執行結果,或下載附件執行檢視,這裡不再貼圖。

2.3 LINQ本質:生成對應的標準查詢運算子

  作為一個細心的.Net碼農,我們不由得對LINQ表示式為我們做了哪些工作而好奇?於是,我們又想起了我們的“滑板鞋”—Reflector或ILSpy,去看看編譯器為我們做了什麼事!

  (1)以上述的基本條件查詢程式碼為例,我們看到原來編譯器將LINQ生成了對應的標準查詢運算子,即Where擴充套件方法:

  (2)再來看看排序條件查詢的程式碼,也是生成了對應的標準查詢運算子,即OrderBy擴充套件方法:

  (3)總結:LINQ編譯後會生成對應的標準查詢運算子(查詢->Where,排序->OrderBy,連線->Join,分組->GroupBy),所以LINQ表示式其實就是類似於SQL風格的一種更加友好語法糖而已。其本質還是擴充套件方法、泛型委託等“舊酒”,被一個“新瓶子”所包裝了起來,就變得高大上了。

系列總結

  轉眼之間,四篇文章的介紹就到此結束了,其實本系列介紹的都是不算新語法,其實也可以說成是老語法了。說它們新,只不過是相對於.NET老版本而言,而且平時開發中大家有可能沒有注意到的一些細節,本系列做了一個簡單的介紹。這幾天看到很多園子裡的童鞋開始關注C# 6.0的新特性了,粗略看了看,語法糖居多,相信經過了這一系列的探祕,對於新的語法糖,我們可以站在一個比較高的高度去看待它們。最後,謝謝各位園友的瀏覽,以及給我的一些鼓勵,再次感謝!

參考文章

附件下載

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

.NET那些所謂語法標準查詢運算子LINQ

開篇:在上一篇中,我們瞭解了預定義委託與Lambda表示式等所謂的新語法,這一篇我們繼續征程,看看標準查詢運算子和LINQ。標準查詢運算子是定義在System.Linq.Enumerable類中的50多個為IEnumerable<T>準備的擴充套件方法,而LINQ則是一種類似於SQL風格的查詢表示

.NET那些所謂語法系統預定義委託Lambda表示式

開篇:在上一篇中,我們瞭解了匿名類、匿名方法與擴充套件方法等所謂的新語法,這一篇我們繼續征程,看看系統預定義委託(Action/Func/Predicate)和超愛的Lambda表示式。為了方便碼農們,.Net基類庫針對實際開發中最常用的情形提供了幾個預定義好的委託,這些委託可以直接使用,無需再重頭定義一個自

.NET那些所謂語法匿名類、匿名方法擴充套件方法

開篇:在上一篇中,我們瞭解了自動屬性、隱式型別、自動初始化器等所謂的新語法,這一篇我們繼續征程,看看匿名類、匿名方法以及常用的擴充套件方法。雖然,都是很常見的東西,但是未必我們都明白其中蘊含的奧妙。所以,跟著本篇的步伐,繼續來圍觀。 /* 新語法索引 */ 一、匿名類:[ C# 3.0/.NET 3.

.NET那些所謂語法之一自動屬性、隱式型別、命名引數自動初始化器

開篇:在日常的.NET開發學習中,我們往往會接觸到一些較新的語法,它們相對以前的老語法相比,做了很多的改進,簡化了很多繁雜的程式碼格式,也大大減少了我們這些菜鳥碼農的程式碼量。但是,在開心歡樂之餘,我們也不禁地對編譯器內部到底為我們做了哪些事兒而感到好奇?於是,我們就藉助反編譯神器,去看看編譯器到底做了啥事!

ALSA音效卡驅動的DAPM詳解在驅動程式初始化並註冊widget和route

前幾篇文章我們從dapm的資料結構入手,瞭解了代表音訊控制元件的widget,代表連線路徑的route以及用於連線兩個widget的path。之前都是一些概念的講解以及對資料結構中各個欄位的說明,從本章開始,我們要從程式碼入手,分析dapm的詳細工作原理: 如何註冊widg

objective-c 數據類型 字典(NSDictionary)

bject ted ray 初始化 -c lec com lock led // 1. 字典初始化、賦值方式1 NSMutableDictionary *m_dictionary = [[NSMutableDictionary alloc] initWithCa

ES6語法---對象字面量擴展、模板字符串(5)

ons 可靠的 小數 為我 寫法 define 當前 BE dde 這節課學習ES6中對象字面量擴展和新增模板字符串 第一部分:對象字面量擴展 1.簡潔寫法   ES6對於對象字面量屬性提供了簡寫方式。   1.1:屬性簡寫 //傳統寫法

跟我學ASP.NET MVC使用Razor

ima pre 技術分享 C# 圖模型 med 執行 sys fonts 摘要: 視圖引擎處理ASP.NET內容,並查找指令,典型情況是向瀏覽器輸出插入動態內容。MVC框架視圖引擎的名字是Razor。 在本文中,我將帶領讀者快速認識Razor,以後你們看到他們的時候能夠

Sql語法高級應用使用視圖實現多表聯合數據明細

sele inner receiver rod erp upn pen logistic received 之前章節我們講到:如果某個表的數據是多個表的聯合,並且存在列與列的合並組成新列,用視圖是最好的方案。 下面我分享兩個個真實的SQL語句案例 USE Wot_In

osgi.net從入門到精通系列

完整 end tel bin host 存在 我們 point sgi 模塊清單文件(Manifest.xml)位於模塊標準目錄結構的根目錄之下,它定義了模塊的 基本信息、模塊激活信息、模塊類加載相關的運行時信息、服務定義信息、模塊擴展定義信息 以及模塊詳細信息。這一小節

[引擎]unity檢視簡單mesh頂點順序的小工具——修改

https://blog.csdn.net/yanchezuo/article/details/78978884 1 新增檢視頂點位置和uv資訊 2 相容頂點數量和uv數量不同的情況 3 新增是否顯示資訊的選項 4 完整程式碼 5 最後 這裡製作的小工具,功能有點單薄,只能檢視頂點的順序。&nb

UVM暫存器篇暫存器模型的整合(

本文轉自:http://www.eetop.cn/blog/html/28/1561828-6266221.html MCDF暫存器模組程式碼 下面我們給出實現後的MCDF暫存器RTL設計程式碼:     上面的設計中採取了巨集的

SAP ABAP7.50語法OPEN SQL第二篇

當然你可以直接關注我的公眾號:SAP Technical 更多內容關注公眾號:SAP Technical 當使用CDS實體的名稱作為資料來源訪問SELECT中的CDS檢視時,此檢視在其SELECT列表中釋出關聯_assoc以供外部使用,則這些關聯可用作路徑表示式的根元素。同樣的宣告

【第一篇】SAP ABAP7.50語法預定義數據結構

什麽 直接 ica 法規 -o top 語法 沖突 技術 前言部分 先說一下,之前有些文章被轉載之後也沒有註明,這個就比較不好。如果你覺得本文寫的並不好,那麽可以直接去看HELP,這樣更直接,我這裏只是做記錄,如果讀者朋友感興趣,可以關註公眾號,也可以在本文末留言,畢竟誰

【第一篇】SAP ABAP7.50語法預定義資料結構

原文連結:SAP ABAP7.50系列之預定義資料結構 公眾號:SAP Technical 前言部分 先說一下,之前有些文章被轉載之後也沒有註明,這個就比較不好。如果你覺得本文寫的並不好,那麼可以直接去看HELP,這樣更直接,我這裡只是做記錄,如果讀者朋友感興趣,可以關注公眾號,也可以在本文末留言,畢竟

【第二篇】SAP ABAP7.50語法OPEN SQL

原文連結:SAP ABAP7.50系列之OPEN SQL 公眾號:SAP Technical 前言部分 當使用CDS實體的名稱作為資料來源訪問SELECT中的CDS檢視時,此檢視在其SELECT列表中釋出關聯_assoc以供外部使用,則這些關聯可用作路徑表示式的根元素。同樣的宣告。在路徑表示式中,關聯名

【第三篇】SAP ABAP7.50語法程式結構&SubScreen

原文地址:SAP ABAP7.50系列之程式結構&SubScreen 公眾號:SAP Technical 前言部分 我們知道,在SAP裡的程式結構包含了很多內容,比如:Global Declarations裡包含interface,classes,global data;Method裡包含loc

【第五篇】SAP ABAP7.50語法命名規約

原文連結:SAP ABAP7.50系列之命名規約 公眾號:SAP Technical 命名約定 以下約定適用於ABAP程式中所有可定義物件的名稱,例如資料型別,資料物件,類,巨集或儲存過程: 1、名稱最長可達30個字元。2、允許的字元是從“A”到“Z”的字母,從“0”到“9”的數字和下劃線(_)。3、

【第七篇】SAP ABAP7.50語法F4增強

公眾號:SAP Technical 本文作者:matinal 原文出處: http://www.cnblogs.com/SAPmatinal/ 原文連結: SAP ABAP7.5x系列之F4增強   前言部分 在ABAP專案裡面,F4搜尋幫助是最常用的功能,

CityEngine CGA語法坡式屋頂函式 roofHip

概要 roofHip( angle ) roofHip( angle, overhang ) roofHip( angle, overhang, even ) 引數 angle (fl