zoukankan      html  css  js  c++  java
  • LINQ标准查询操作符学习笔记

            Linq是语言集成查询的简称。Linq简化了语言查询,是的程序员可以使用相同的语法查询不同的数据源。

            本文是闲暇之余学习Linq的产物。代码是花了大约一周的业余时间组织的,敲出来只是为了记住它。本文使用的Linq查询包含了两种方式,一种是直接查询,另一种是使用System.Linq中定义的扩展方法进行的查询。对于扩展方法查询的方式,使用了大量的Lambda表的是和系统定义的委托Func<T>。不熟悉这两个东西的童鞋们可以在本博找到相关的文章:

    Lambda表达式学习记录】【Action<T>和Func<T>委托

    一、准备供查询的数据 

    本文首先构建一个提供数据的实体类供以后的查询使用。本文使用的实体类包含了1950到2008年一级方程式锦标赛的冠军车队和冠军赛手。这些数据是使用了实体类和列表来准备的。

    首先定义代表赛手的实体类Racer。Racer类定义了几个属性和一个重载的ToString()方法,该方法以指定的格式显示赛手。Racer类实现了IFormattable接口,以支持格式字符串的不同变体。这个类还实现类IComparable<T>接口,用以根据赛手的LastName进行排序。为了执行高级查询Racer类同时包含了单值属性如FirstName、LastName、Wins(获胜次数)、Country(国籍)和Starts。同时也包含了多值属性如:Cars(赛手在获得冠军的年份所使用的赛车)和Years(赛手获得冠军的年份,赛手可以多次获得冠军)。这个类具体的实现如下:

    Racer

    第二个实体类是Team。这个类包含了车队冠军的名字和获得冠军的年份。

    Racer

    第三个类是Formula1.这个类定义方法GetChampions方法返回一组表示以及方程式赛车冠军的列表。

    Racer

    这个类还定义了一个方法:GetConstructorChampions()。用于返回所有车队冠军的列表。

            二、System.Linq中的扩展方法 

           扩展方法的作用是将方法写入最初没有定义该方法的类中。还可以将方法添加到实现某个特定接口的任何类中,这样多个类就可以使用相同的实现代码。对于扩展方法本文不做详细介绍,只是扩展方法在Linq查询中使用较多需要预先了解。尤其是System.Core程序集下定义的System.Linq方法。本文后边将会用到。

            三、使用Linq进行查询 

    有了这些预备知识和实体类,我们就可以使用Linq进行查询了。

           1、简单的筛选

    使用Where子句可以对数据源进行简单的筛选。下变例子是找出至少赢得15场比赛的奥地利车手:

            /// <summary>
    /// 1、最简单的查询
    /// </summary>
    static void LinqQuery1()
    {
    var query = from r in Formula1.GetChampions()
    where (r.Country == "Brazil" || r.Country == "Austria") && r.Wins > 15
    orderby r.Wins descending
    select r;
    foreach (var racer in query)
    {
    Console.WriteLine("{0:A}", racer);
    }
    }

    这段代码的功能可以使用System.Linq中的扩展方法来实现:

    【代码】

                var query2 = Formula1.GetChampions()
    .Where(r => r.Country == "Brazil" || r.Country == "Austria")
    .Select(r=>r);

    注:并不是所有的查询都可以使用Linq查询或者扩展方法都可以实现的。高级查询需要使用扩展方法。

           2、用索引进行筛选

    Where()扩展方法的一个重载中,可以对该方法传递第二个参数:索引。索引是筛选器返回的每个结果的计数器,可以在表达式中使用索引,执行一些索引相关的计算。现编的代码使用Where()扩展方法,返回姓氏以A开头,索引为偶数的车手:

            /// <summary>
    /// 2、使用索引i进行筛选
             /// </summary>
    static void LinqQuery2()
    {

    //使用索引i进行筛选
                 var query = Formula1.GetChampions()
    .Where((r, i) => r.LastName.StartsWith("A") && i % 2 != 0);
    foreach (var item in query)
    {
    Console.WriteLine("{0:A}", item);
    }
    }

           3、筛选出不同的数据类型

    使用OfType()扩展方法,可以实现基于类型的筛选。下面的代码中定义了包含了string和int类型的对象,使用OfType()方法从集合中找出字符串:

            /// <summary>
    /// 3、类型筛选
             /// </summary>
    static void LinqQuery3()
    {
    object[] data = { "one", 1, 2, 3, "two", "three" };
    var query = data.OfType<string>();

    foreach (var item in query)
    {
    Console.WriteLine(item);
    }
    }

           4、复合的from子句

    复合的from子句用于对这样一种情况的查询:需要根据对象的一个成员进行筛选,而这个成员本身是一组数据。

    本例中,Racer类定义的属性Cars就是这样的一个属性。Cars是一个字符串数组。

    使用如下的Linq查询可以筛选出嘉实法拉利的所有冠军:

                var query = from r in Formula1.GetChampions()
    from c in r.Cars
    where c == "Ferrari"
    orderby r.LastName
    select r;

    其中,第一个from子句用于访问从Formyla1.GetChampions方法返回的Racer对象,第二个from子句访问Racer类的Cars属性,返回所有string类型的赛车,最后使用Where子句从这些赛车中筛选出所有冠军。

    这里C#编译器将符合的from子句和Linq查询转换成SelectMany()方法,SelectMany()方法可以用于迭代序列的序列。使用SelectMany()扩展方法的代码如下:

                var query = Formula1.GetChampions()
    .SelectMany(r => r.Cars, (r, c) => new { Racer = r, Car = c })
    .Where(r => r.Car == "Ferrari")
    .OrderBy(r => r.Racer.FirstName)
    .Select(r => r.Racer.FirstName + " " + r.Racer.LastName);

    这里SelectMany()方法的第一个参数是隐式参数,他从GetChampions方法中接收Racer对象序列,第二个参数是collectionSelector委托,其中定义了内部序列。在Lambda表达式r=>r.Cars中,返回赛车集合。第三个参数是一个Func<T>委托,这里为每个Car调用该委托接收Racer和Cars对象。Lambda表达式创建了一个包含了Racer和Cars属性的匿名的类型。这个SelectMany方法的结果摊平了赛手和赛车的层次结构,为每个赛车返回匿名类型的一个新对象耦合。

    这段解释有点拗口,因为Lambda表达式确实比较难以解释,还有使用了几个Func<T>委托,不了解委托看这段代码简直就是天书。不过VS的职能提示挺管用的,在敲出几个代码之后看看他的提示在继续写也是不错的。

           5、对筛选结果进行排序

    使用orderby和orderby descending子句或者OrderBy()和OrderByDescending()扩展方法可以实现对筛选结果的排序。

    下面这段代码是对筛选出来的赛手使用赢得比赛的次数进行排序:

                //简单的Linq语句查询排序
    var query1 = from r in Formula1.GetChampions()
    where r.Country == "Brazil"
    orderby r.Wins descending
    select r;

    转换成扩展方法后的实现:

                //使用扩展方法
    var query2 = Formula1.GetChampions()
    .Where(r => r.Country == "Brazil")
    .OrderByDescending(r => r.Wins)
    .ThenByDescending(r=>r.LastName)
    .Select(r => r);

    最后还可以使用Take方法对结果进一步筛选:

                //使用Take子句提取前十项数据
    var query3 = (from r in Formula1.GetChampions()
    orderby r.Country, r.LastName, r.FirstName
    select r)
    .Take(10);

    //使用扩展方法查询
    var query = Formula1.GetChampions()
    .OrderBy(r => r.Country)
    .ThenBy(r => r.LastName)
    .ThenBy(r => r.FirstName)
    .Take(10)
    .Select(r => r);

           6、对筛选结果进行分组

    使用group子句和GroupBy()扩展方法可以对查询结果进行分组。下边的例子是将赛车冠军按照国家进行分组,并列出一个国家赛车冠军的总数:

                //简ò单蹋inq分?组哩?
    var query = from r in Formula1.GetChampions()
    group r by r.Country into g
    orderby g.Count() descending, g.Key
    where g.Count() >= 2
    select new
    {
    Country = g.Key,
    Count = g.Count()
    };

    子句group r by r.Country into g 根据Country属性组合所有赛车手到一个新的标识符g中。g用于以后访问分组的结果信息。group子句的结果根据扩展方法Count()的结果进行排序。Select子句创建了一个带Country和Count属性的匿名类型。这里有一个对象需要注意:g.Key。这指的是是group方法筛选的依据,本例中g.Key就是分组依据Country。

    直接使用扩展方法中的GroupBy()和ThenBy()方法也可以实现相应的筛选功能:

                var query = Formula1.GetChampions()
    .GroupBy(r => r.Country)
    .OrderByDescending(g => g.Count())
    .ThenBy(g => g.Key)
    .Where(g => g.Count() >= 2)
    .Select(g => new { Country = g.Key, Count = g.Count() });

    这里可以看出子句group r by r.Country into g被解析为GroupBy(r=>r.Country),返回分组序列,之后进行了排序和筛选等见大的操作即得到了需要的结果。

           7、对嵌套对象进行分组

    如果分组的对象应包含嵌套的序列,就可以改变select子句创建的匿名类型。先看下面的代码:

                var countrys =
    from r in Formula1.GetChampions()
    group r by r.Country into g //g是?按恪?照?国ú别纄分?组哩?后ó的?Formula1.GetChampions()
    orderby g.Count() descending, g.Key //g.Key指?的?是?分?组哩?依皑?据Yr.Country 即′r中D的?Country
    where g.Count() >= 2
    select
    new
    {
    Country = g.Key,
    Count = g.Count(),
    Racer = from r1 in g
    orderby r1.LastName
    select r1.FirstName + " " + r1.LastName
    };

    在上面的例子中,返回的国家不仅包含国家名和赛手数量这两个属性,还包括赛手姓名序列。这个序列用一个赋予Racers属性的from/in内部子句指定,内部的from子句使用分组标识符g获取该分组中的所有赛车手,并排序,再根据姓名创建一个新的字符串返回。

    这段代码转换成扩展方法可表示如下:

                var countrys = Formula1.GetChampions()
    .GroupBy(r => r.Country)
    .OrderByDescending(g => g.Count())
    .ThenBy(g => g.Key)
    .Where(g => g.Count() >= 2)
    .Select(g => new
    {
    Country = g.Key,
    Count = g.Count(),
    Racer = g.OrderBy(r => r.LastName).Select(r => r.FirstName + " " + r.LastName)
    });

           8、连接查询

    使用join子句可以根据特定的条件合并两个数据源,但之前要获得两个要连接的列表。

    在一级方程式比赛中,有比赛冠军和车队冠军,下边的代码用来筛选出每年的赛手冠军和车队冠军。实现这一功能有几种方法,下面一一列举:

    (1)使用多次查询

    先定义两个查询,分别找出2003年之后每年的赛手冠军和车队冠军:

    //1、查询1
     var racer = from r in Formula1.GetChampions()
    from y in r.Years
    where y > 2003
    select new
    {
    Year = y,
    Name = r.FirstName + " " + r.LastName
    };

    //2、查询2
     var teams = from t in Formula1.GetConstructorChampions()
    from y in t.Years
    where y > 2003
    select new
    {
    Year = y,
    Name = t.Name
    };

    之后在通过join in …on…进行连接:

    //3、将两次查询结果连接
    var query = from r in racer
    join t in teams on r.Year equals t.Year
    select new
    {
    Year = r.Year,
    Racer = r.Name,
    Team = t.Name
    };

    (2)也可将两次查询合并为一个Linq查询 ,不过比较麻烦的说:

    //使用一条语句完成所有连接查询
    var query2 = from r in
    from
    r1 in Formula1.GetChampions()
    from yr in r1.Years
    where yr > 2003
    select new
    {
    Year = yr,
    Name = r1.FirstName + " " + r1.LastName
    }
    join t in
    from
    t1 in Formula1.GetConstructorChampions()
    from yt in t1.Years
    where yt > 2003
    select new
    {
    Year = yt,
    Name = t1.Name
    }
    on r.Year equals t.Year
    select new
    {
    Year = r.Year,
    Racer = r.Name,
    Team = t.Name
    };

           9、集合查询

    System.Linq中的扩展方法Distinct()、Union()、Intersect()、Except()的都是集合操作的方法。这些方法使用不难,主要涉及到一些对集合操作的理解上。

    下面用一段代码演示集合操作符的使用:

    //1、先进性一次简单的查询
    var query = from r in Formula1.GetChampions()
    from y in r.Years
    where y > 2003
    select r;
    //2、再对结果进行集合操作.这里的几个操作没有实际意义,只是为了演示用法。
     var q = query.Union(query).Distinct().Intersect(query);

    上面的代码中,先进行了一个间的的查询,之后对查询结果进行了集合操作。以下是上面代码转换成使用扩展方法的代码:

     //3、直接使用扩展方法进行查询
    var query2 = Formula1.GetChampions()
    .SelectMany(r => r.Years, (r, y) => new { Racer = r, Year = y }) //使用了SelectMany()嵌套查询
    .Where(r => r.Year > 2003)
    .Select(r => r.Racer)
    .Union(query).Distinct().Intersect(query); //仅演示,没实际意义

           10、合并

    合并操作Zip()是.NET4新增的。这个操作运行用一个谓词函数把两个相关的序列合并为一个。我不是很理解这个合并操作,仅给出树上的代码,不做解释:

    var racerNames = from r in Formula1.GetChampions()
    where r.Country == "Italy"
    orderby r.Wins descending
    select new
    {
    Name = r.FirstName + " " + r.LastName
    };
    var racerNameAndStarts = from r in Formula1.GetChampions()
    where r.Country == "Italy"
    orderby r.Wins descending
    select new
    {
    LastName = r.LastName,
    Starts = r.Starts
    };
    //.Net4中的Zip()方法。用一个谓词函数把两个相关的序列合并为一个。注意Zip的参数
    var racers = racerNames.Zip(racerNameAndStarts, (first, second) => first.Name + ",starts " + second.Starts);
    foreach (var item in racers)
    {
    Console.WriteLine(item);
    }

           11、分区

    扩展方法Take()和Skip()等用于分区操作,这些操作可用于数据分页。在下面的Linq查询中,把扩展方法添加到查询最后,Skip()方法或略掉根据分页大小和页数得到的项数,Take()方法根据分页大小提取一定数目的项:

    int pagesize = 5;
    int numberPages = (int)Math.Ceiling(Formula1.GetChampions().Count / (double)pagesize);
    for (int i = 0; i < numberPages; i++)
    {
    Console.WriteLine("Page {0}", i);
    var racers = (from r in Formula1.GetChampions()
    orderby r.LastName
    select r.FirstName + " " + r.LastName)
    .Skip(i * pagesize) //跳?过y指?定¨数y目?的?元a素?
    .Take(pagesize) //提á取?指?定¨数y目?的?元a素?
    .TakeWhile(r => r.Length > 11); //额?外a的?限T制?条?件t
    foreach (var item in racers)
    {
    Console.WriteLine(item);
    }
    Console.WriteLine();

    输出结果是几组赛手。

           12、聚合操作

    聚合操作用于对集合项进行简单的运算,返回结果是一个值而不是一组数据。

    下面演示使用Count()方法筛选出冠军次数操作3的赛手:

    //统计获得冠军次数大于3次的运动员和获胜次数
    var query = from r in Formula1.GetChampions()
    where r.Years.Count() > 3
    orderby r.Years.Count() descending
    select new
    {
    Name = r.FirstName + " " + r.LastName,
    TimesChampion = r.Years.Count()
    };
    foreach (var item in query)
    {
    Console.WriteLine(item.Name + " " + item.TimesChampion.ToString());
    }
    Console.WriteLine();

    又如Sum()方法,返回序列中的所有数字的和。下面的示例演示了使用Sum()方法计算一个国际赢得的比赛次数,首先根据国家对赛手分组,在创建的匿名类中,对Wins属性进行计算:

    //计算一个国际获得冠军的总次数
    var countries =
    from c in
    from
    r in Formula1.GetChampions()
    group r by r.Country into c1
    select new
    {
    Country = c1.Key,
    Wins = (from r1 in c1 select r1.Wins).Sum()
    }
    orderby c.Wins descending
    select
    c;

           13、结果转换

    查询结果可以通过调用扩展方法转换成其他类型。但是需要注意的是,如果进行了类型转换,Linq查询将会立即执行而不是推迟到访问数据项时才执行。

    下面是一个简单的例子:

    //简单查询
    var query = (from r in Formula1.GetChampions()
    where r.Starts > 150
    orderby r.Starts descending
    select
    r).ToList();
    foreach (var item in query)
    {
    Console.WriteLine("{0} {0:S}",item);
    }
    Console.WriteLine();

    //扩展方法查询
    var query2 = Formula1.GetChampions()
    .Where(r => r.Starts > 150)
    .OrderByDescending(r => r.Starts)
    .ToList()
    .Select(r => r) ;
    foreach (var item in query2)
    {
    Console.WriteLine("{0} {0:S}", item);
    }
    Console.WriteLine();
    //生成查找表
    var query3 = (from r in Formula1.GetChampions()
    from c in r.Cars
    select new { Car = c, Racer = r })
    .ToLookup(cr => cr.Car, cr => cr.Racer);
    if (query3.Contains("Williams"))
    {
    foreach (var item in query3["Williams"])
    {
    Console.WriteLine(item);
    }
    }

    这里建查询结果使用ToList()方法转换成了List<T>类型。

           14、生成操作符

    生成操作符Ranger()、Empty()、Repear()不是扩展方法,而是返回序列的正常静态方法。

    下面例子返回一个填充了一个范围数字的变量,使用了Ranger()方法对变量进行填充

    //var values = Enumerable.Range(1, 12);
    var values = Enumerable.Range(1, 12)
    .Where(r=>r%2 == 0)
    .Select(r => r * 2);
    foreach (var item in values)
    {
    Console.Write("{0} ",item);
    }
    Console.WriteLine();

           15、并行Linq

    并行Linq是.Net4新增加的。在.Net 4的System.Linq命名空间中新增加了类ParallelEnumerable,可以分解查询工作使其工作在多个线程上。

    这里首先准备一个大型集合,用随机数填充之后使用Linq筛选数据,获取筛选数据的总和。该查询使用where子句定义一个筛选器,汇总值小于20的项,最后使用Sum()方法计算: 

    const int arraySize = 100000000;
    var data = new int[arraySize];
    var r = new Random();
    for (int i = 0; i < arraySize; i++)
    {
    data[i] = r.Next(40);
    }
    var sum = (from x in data.AsParallel()
    where arraySize < 20
    select x).Sum();
    sum = data.AsParallel().Where(x => x > 20).Select(x => x).Sum();
    Console.WriteLine(sum);

    可以看出,这里的查询与前面的查询仅仅是使用了AsParallel()方法。

    运行这段代码时启动任务管理器就可以看见效果了。此时系统上所有的CPU都处于忙碌状态,如果删除了AsParallel方法,就不会有这个效果了。

    终于写完了,最后感谢你看了我的文字并附上本文所用的代码:

    https://files.cnblogs.com/zyqgold/MyLinqTest.rar

    Technorati 标签: Linq
  • 相关阅读:
    打印出从1到最大的n位十进制数
    排序算法--(二)
    排序算法 (-)
    两个栈实现一个队列
    C++ 模板类解析
    根据先序遍历中序遍历重建二叉树
    格式化时间
    用js实现冒泡排序
    接口和抽象类的区别
    解析json
  • 原文地址:https://www.cnblogs.com/zyqgold/p/1936382.html
Copyright © 2011-2022 走看看