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

開篇:在上一篇中,咱們瞭解了預約義委託與Lambda表達式等所謂的新語法,這一篇咱們繼續征程,看看標準查詢運算符和LINQ。標準查詢運算符是定義在System.Linq.Enumerable類中的50多個爲IEnumerable<T>準備的擴展方法,而LINQ則是一種相似於SQL風格的查詢表達式,它們能夠大大方便咱們的平常開發工做。所以,須要咱們予以關注起來!html

/* 新語法索引 */

9.標準查詢運算符 Standard Query Operator
10.LINQ查詢表達式

1、擴展方法哪家強?標準查詢運算符:[ 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方法實現以下:ide

        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集合返回。ui

1.2 投影大牛Select方法

  Select方法能夠查詢投射,返回新對象集合。這裏,假設咱們先篩選出全部男性集合,再根據男性集合中全部項的姓名生成子集合(這是一個不一樣於原類型的類型),就能夠藉助Select方法來實現。spa

        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)運行結果以下圖所示:code

  (2)這裏也能夠採用匿名類,能夠省去事先聲明LitePerson類的步湊,但須要配合var使用:orm

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

  (3)這裏由於實現LitePerson類重寫了ToString()方法,因此這裏直接調用了ToString()方法。htm

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("奶茶"));

2、查詢方式誰更快?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的新特性了,粗略看了看,語法糖居多,相信通過了這一系列的探祕,對於新的語法糖,咱們能夠站在一個比較高的高度去看待它們。最後,謝謝各位園友的瀏覽,以及給個人一些鼓勵,再次感謝!

參考文章

  (1)MSDN,《標準查詢運算符概述》:http://msdn.microsoft.com/zh-cn/library/bb397896.aspx

  (2)MSDN,《LINQ(語言繼承查詢)》:http://msdn.microsoft.com/library/bb397926.aspx

  (3)jake強,《爲提升EF性能須要注意哪些事情?》:http://www.cnblogs.com/jake1/archive/2013/04/25/3043664.html

附件下載

  NewGrammerDemo-v1.3:http://pan.baidu.com/s/1pJnyKtX

 

相關文章
相關標籤/搜索