C# 标准查询运算符

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

 

 

 

出处:http://edisonchou.cnblogs.com

posted @ 2020-05-29 11:50  delafqm  阅读(368)  评论(0编辑  收藏  举报