code_philo

导航

LINQ基本语法详解

    语言集成查询(LINQ)是一组将查询功能直接集成到C#语言的技术的总称。借助于LINQ,查询现在和类、方法、事件一样,是高级语言结构。

    对于编写查询的人来说,LINQ 最明显的“语言集成”部分是查询表达式。 查询表达式是在 C# 3.0 中引入的声明性查询语法。 通过使用查询语法,您可以使用最少的代码对数据源执行复杂的筛选、排序和分组操作,还可以用来查询和转换 SQL 数据库、ADO.NET 数据集、XML 文档以及 .NET 集合中的数据。

    下面的示例演示了完整的查询操作。 完整操作包括创建数据源、定义查询表达式,以及在 foreach 语句中执行查询。

class LINQQueryExpressions
{
    static void Main()
    {

        // Specify the data source.
        int[] scores = new int[] { 97, 92, 81, 60 };

        // Define the query expression.
        IEnumerable<int> scoreQuery =
            from score in scores
            where score > 80
            select score;

        // Execute the query.
        foreach (int i in scoreQuery)
        {
            Console.Write(i + " ");
        }            
    }
}
// Output: 97 92 81

1. 查询表达式基础

1.1 什么是查询

    “查询”是一组描述从指定源数据检索什么数据以及返回的数据以什么格式什么形式返回。查询不等同于查询结果。

    通常,源数据为相同种类元素的集合。如 SQL 数据库表包含一个行序列,ADO.NET DataTable 包含一个 DataRow 对象序列,XML 文件包含一个 XML 元素“序列”(按树结构组织), 内存中的集合包含一个对象序列。

    从程序的角度来看,源数据的具体类型和结构并不重要,应用程序始终将其视为一个 IEnumerable<T>IQueryable<T> 集合。在 LINQ-XML 中,源数据为一个 IEnumerable<XElement>;在 LINQ-DataSet 中,源数据为一个 IEnumerable<DataRow>;在 LINQ-SQL 中,源数据为您定义的用来表示 SQL 表中数据的对象的 IEnumerableIQueryable

    指定此源序列后,查询可以执行下列三项工作:

  • 检索源数据的一个子集,不修改当个元素。然后,可以对结果结构进一步排序或分组后返回。如下所示(假定 scores 是 int[]):
    IEnumerable<int> highScoresQuery =
        from score in scores
        where score > 80
        orderby score descending
        select score;
  • 按上面的方式检索,但是将结果以新的数据类型返回.例如,可以只检索源数据中的customer的姓氏,或者检索完整记录,再使用该记录构建新的数据类型,甚至可以构建XML数据,然后生成最终的结果。下面演示如何将int 转换为string。注意 highScoresQuery的类型。
    IEnumerable<string> highScoresQuery2 =
        from score in scores
        where score > 80
        orderby score descending
        select String.Format("The score is {0}", score);
  • 检索有关源数据特性的单个值,如:
    • 满足条件元素的个数。
    • 所有元素中的最大值或最小值。
    • 符合某个条件的第一个元素,或所有元素之和等。下面演示从 scores 整数数组中得到大于80的分数的数目。
int highScoreCount =
    (from score in scores
     where score > 80
     select score)
     .Count();

    应该注意到,上面调用Count 方法之前的查询表达式用括号括起来了。另一种方式是使用一个新的变量存储具体结果,这种方式可读性更好,将查询变量与查查询结果变量区分开来了:

IEnumerable<int> highScoresQuery3 =
    from score in scores
    where score > 80
    select score;

int scoreCount = highScoresQuery3.Count();

1.2 什么是查询表达式

    “查询表达式”即用查询语法表示的查询。查询表达式由一组类似于SQL或XQuery的声明性语法编写的子句组成。每个子句又包含一个或多个C#表达式,而这些表达式本身也可能包含查询表达式。

    查询表达式必须以 from 子句开头,以 selectgroup 子句结尾。 在from 子句和select 或 group 子句之间,可以包含一个或多个下列子句:whereorderbyjoinlet ,也可以包含附加的from 子句。 还可以使用 into 关键字使 join 或 group 子句的结果作为同一查询表达式中附加查询子句的源。

 

1.2.1查询变量

    在 LINQ 中,查询变量即存储查询(而非查询结果)的变量。具体地说,查询变量是一个枚举类型,即在 foreach 语句中迭代或直接调用 IEnumerator.MoveNext 方法的循环访问它时,会生成一个元素序列的类型。

    下面的代码示例演示了一个简单的查询表达式,它包含一个数据源、一个筛选子句和一个排序子句,以select 子句结束了该查询。

static void Main()
{
    // 数据源.
    int[] scores = { 90, 71, 82, 93, 75, 82 };

    // 查询表达式.
    IEnumerable<int> scoreQuery = //查询变量
        from score in scores //required
        where score > 80 // optional
        orderby score descending // optional
        select score; //must end with select or group

    // 执行查询,生成结果
    foreach (int testScore in scoreQuery)
    {
        Console.WriteLine(testScore);
    }                  
}
// Outputs: 93 90 82 82

    在上例中,scoreQuery 是一个查询变量,简称为“查询”。 查询变量并不存储实际的查询结果(查询结果在 foreach 循环中产生的)。 另外,当 foreach 语句执行时,查询结果并不是通过查询变量 scoreQuery 返回的, 而是通过迭代变量 testScore 返回。 如果在另一个 foreach 循环中迭代 scoreQuery 变量, 只要该变量和数据源都没有修改,将产生相同的结果。

    查询变量可以存储以查询语法或方法语法(或二者的组合)表示的查询。 在下例中,queryMajorCities queryMajorCities2 都是查询变量:

//查询语法
IEnumerable<City> queryMajorCities =
    from city in cities
    where city.Population > 100000
    select city;

// 方法语法
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);

    另外,下面的两个例子中演示的两个变量都不是查询变量,虽然它们都是用查询表达式进行了初始化。 它们表示查询结果:

int highestScore =
    (from score in scores
     select score)
    .Max();

// or split the expression
IEnumerable<int> scoreQuery =
    from score in scores
    select score;

int highScore = scoreQuery.Max();

List<City> largeCitiesList =
    (from country in countries
     from city in country.Cities
     where city.Population > 10000
     select city)
       .ToList();

// or split the expression
IEnumerable<City> largeCitiesQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

List<City> largeCitiesList2 = largeCitiesQuery.ToList();

说明 NOTE: 在 LINQ 文档中,查询变量在其名称中包含单词“query”, 而查询结果变量在其名称中不包含单词“query”。

查询变量的显式类型化和隐式类型化

      我们这里提供的例子里面查询变量通常为显式类型,以便演示查询变量和 select 子句之间的类型关系。 不过,也可以使用 var 关键字指示编译器推断查询变量的类型:

// Use of var is optional here and in all queries.
// queryCities is an IEnumerable<City> just as 
// when it is explicitly typed.
var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

1.2.2 查询表达式的开始

      查询表达式必须以 from 子句开头。 from 子句指定了数据源和范围变量。 在对源序列进行遍历时,范围变量表示源序列中的单个元素,并根据数据源中元素的类型对其进行强类型化。 在下面的示例中,因为countries 是 Country 的对象数组,所以范围变量被类型化为 Country, 这样就可以使用点运算符来访问该类型的任何可用成员。

IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 500000 //sq km
    select country;

      查询表达式可以包含多个 from 子句。 当源序列中的每个元素本身就是集合或包含集合时,可使用附加的 from 子句。 例如,假定您具有一个 Country 对象集合,而其中每个对象都包含一个名为 Cities 的 City 对象集合。 若要查询每个 Country 中的 City 对象,则使用两个from 子句,如下所示:

IEnumerable<City> cityQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

1.2.3 查询表达式的结束

      查询表达式必须以 select 子句或 group 子句结尾。

group 子句

      使用 group 子句可使结果按指定的键进行分组。 键值可以采用任何数据类型。 例如,下面以 Country 的Name的第一个字符对其进行分组,键值类型为为 char

var queryCountryGroups =
    from country in countries
    group country by country.Name[0];
select 子句

      使用 select 子句能使结果为其他类型。 简单的 select 子句得到的结果类型不变。如下,数据源包含 Country 对象。 orderby 子句将元素重新排序,而 select 子句仅仅返回重新排序的 Country 对象的序列。

IEnumerable<Country> sortedQuery =
    from country in countries
    orderby country.Area
    select country;

      使用 select 子句将源数据转换为新的类型, 这一过程称为“投影”。如下,select 子句包含原始数据部分字段的匿名类型进行投影:

// 此处 var 是必须的,因为查询结果为匿名类型
var queryNameAndPop =
    from country in countries
    select new { Name = country.Name, Pop = country.Population };

1.2.4 使用 ”into”延续操作

      可以使用 into 关键字在 select group 子句中来创建临时标识符用于存储查询。 在分组或选择之后还需要执行附加查询操作时非常有用。如下所示,对 countries 以人口(10 millon为界)进行分组。 创建分组后,需要附加子句筛选掉某些组,然后按升序对剩下的组进行排序。 要完成这些附加操作,新建的临时变量 countryGroup 是必须的:

// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
    from country in countries
    let percentile = (int) country.Population / 10000000
    group country by percentile into countryGroup
    where countryGroup.Key >= 20
    orderby countryGroup.Key
    select countryGroup;

// grouping is an IGrouping<int, Country>
foreach (var grouping in percentileQuery)
{
    Console.WriteLine(grouping.Key);
    foreach (var country in grouping)
        Console.WriteLine(country.Name + ":" + country.Population);
}

1.2.5 筛选、排序和联接

      在 from 开始子句和 select group 结束子句之间,所有其他子句(wherejoinorderbyfromlet)都是可选的。 任何可选子句都可以使用任意次数。

where 子句

使用 where 子句可以根据一个或多个谓词表达式筛选源数据。下例的 where 子句含有两个谓词:

IEnumerable<City> queryCityPop =
    from city in cities
    where city.Population < 200000 && city.Population > 100000
    select city;
orderby 子句

使用 orderby 子句可以对结果进行排序。 还可以指定二级排序顺序。 下例使用 Area 属性对 country 对象执行主要排序, 然后使用 Population 属性执行二级排序:

IEnumerable<Country> querySortedCountries =
    from country in countries
    orderby country.Area, country.Population descending
    select country;

ascending 关键字是可选的;如果未指定顺序,则采用默认排序顺序。

join子句

      使用 join 子句可以根据指定元素对不同数据源进行关联或组合。 在 LINQ 中,联接是专门针对类型不同的数据源设计的,在联接两个序列之后,需要使用 select group 语句指定要输出的元素。 还可以使用匿名类型将每组关联元素中的属性组合为新的输出类型。 下例对prod 对象进行关联,其 Category 属性与 categories 字符串数组中的值进行匹配,如果其 Category 不与 categories 中的任何字符串匹配,则筛去。 select 语句投影了一个新类型:

var categoryQuery =
    from cat in categories
    join prod in products on cat inequals prod.Category
    select new { Category = cat, Name = prod.Name };

通过使用 into 关键字将 join 操作的结果存储到临时变量中,还可以执行分组联接。

let 子句

使用 let 子句可以将表达式(如方法调用)的结果保存到一个新的范围变量中。 在下例中,范围变量 firstName 存储了 Split 返回的字符串数组的第一个元素。

string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" };
IEnumerable<string> queryFirstNames =
    from name in names
    let firstName = name.Split(new char[] { ' ' })[0]
    select firstName;

foreach (string s in queryFirstNames)
    Console.Write(s + " ");
//Output: Svetlana Claire Sven Cesar
查询表达式的子查询

      子句本身可能包含一个查询表达式,该查询表达式称为“子查询”。 每个子查询都以它自己的 from 子句开头,该子句不一定指向第一个 from 子句中的同一数据源。 例如,下面的查询演示了一个在 select 语句中使用的查询表达式,用来检索分组操作的结果。

var queryGroupMax =
    from student in students
    group student by student.GradeLevel into studentGroup
    select new
    {
        Level = studentGroup.Key,
        HighestScore =
            (from student2 in studentGroup
             select student2.Scores.Average())
             .Max()
    };

2. LINQ in C#

在C#中使用LINQ包含3种方式:

  1. 使用查询语法
  2. 使用方法语法
  3. 混合使用查询语法和方法语法

应当尽可能使用(1),必要时使用(2)和(3)

2.1 查询语法

    推荐以查询语法创建查询表达式,下例演示了三个查询表达式,第一个通过where 子句应用筛选条件,第二个对结果进行排序,第三个对结果进行分组:

// Query #1.
List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

// The query variable can also be implicitly typed by using var
IEnumerable<int> filteringQuery =
    from num in numbers
    where num < 3 || num > 7
    select num;

// Query #2.
IEnumerable<int> orderingQuery =
    from num in numbers
    where num < 3 || num > 7
    orderby num ascending
    select num;

// Query #3.
string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans", "barley" };
IEnumerable<IGrouping<char, string>> queryFoodGroups =
    from item in groupingQuery
    group item by item[0];

查询的类型都是 IEnumerable<T>,且都可以通过var替代类型。

2.2 方法语法

某些查询无法以查询语法写,必须以方法调用形式表示。最常见的此类方法是那些返回单一值的方法,如Sum, Max, Min, Average等。这些方法在任何查询中都必须最后才调用,因为它们返回单个值,不能作为其他操作的数据源。如下:

List<int> numbers1 = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
List<int> numbers2 = new List<int>() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 };
// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

如果方法有参数,则参数以Lambda表达式的形式给出,如下:

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

上面的几个例子,只有查询4会立刻执行。因为它返回单个值,而不是泛型集合IEnumerable<T>。方法本身必须使用 foreach 循环才能计算其值。上述的每个查询都可以使用 var 进行隐式类型化:

// var is used for convenience in these queries
var average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

2.3 混合方法

// Query #7.

// Using a query expression with method syntax
int numCount1 =
    (from num in numbers1
     where num < 3 || num > 7
     select num).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num < 3 || num > 7
    select num;

int numCount2 = numbersQuery.Count();

混合语法只需要将查询表达式包含在括号内,然后调用方法。在上面的例子中,查询7返回3到7之间的数字个数。不过,使用另一个变量存储方法调用结果更好。

上面的例子还可以完全按照方法语法进行编写:

var numCount = numbers.Where(n => n < 3 || n > 7).Count();

3. 示例(查询对象集合)

查询第一次考试得分大于90的学生。

public class StudentClass
{
    #region data
    protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear };
    protected class Student
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int ID { get; set; }
        public GradeLevel Year;
        public List<int> ExamScores;
    }

    protected static List<Student> students = new List<Student>
    {
        new Student {FirstName = "Terry", LastName = "Adams", ID = 120, 
            Year = GradeLevel.SecondYear, 
            ExamScores = new List<int>{ 99, 82, 81, 79}},
        new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116, 
            Year = GradeLevel.ThirdYear,
            ExamScores = new List<int>{ 99, 86, 90, 94}},
        new Student {FirstName = "Hanying", LastName = "Feng", ID = 117, 
            Year = GradeLevel.FirstYear, 
            ExamScores = new List<int>{ 93, 92, 80, 87}},
        new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114, 
            Year = GradeLevel.FourthYear,
            ExamScores = new List<int>{ 97, 89, 85, 82}},
        new Student {FirstName = "Debra", LastName = "Garcia", ID = 115, 
            Year = GradeLevel.ThirdYear, 
            ExamScores = new List<int>{ 35, 72, 91, 70}},
        new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118, 
            Year = GradeLevel.SecondYear, 
            ExamScores = new List<int>{ 92, 90, 83, 78}},
        new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113, 
            Year = GradeLevel.FirstYear, 
            ExamScores = new List<int>{ 88, 94, 65, 91}},
        new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112, 
            Year = GradeLevel.FourthYear, 
            ExamScores = new List<int>{ 75, 84, 91, 39}},
        new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111, 
            Year = GradeLevel.SecondYear, 
            ExamScores = new List<int>{ 97, 92, 81, 60}},
        new Student {FirstName = "Lance", LastName = "Tucker", ID = 119, 
            Year = GradeLevel.ThirdYear, 
            ExamScores = new List<int>{ 68, 79, 88, 92}},
        new Student {FirstName = "Michael", LastName = "Tucker", ID = 122, 
            Year = GradeLevel.FirstYear, 
            ExamScores = new List<int>{ 94, 92, 91, 91}},
        new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121,
            Year = GradeLevel.FourthYear, 
            ExamScores = new List<int>{ 96, 85, 91, 60}}
    };
    #endregion

    //Helper method, used in GroupByRange.
    protected static int GetPercentile(Student s)
    {
        double avg = s.ExamScores.Average();
        return avg > 0 ? (int)avg / 10 : 0;
    }


    public void QueryHighScores(int exam, int score)
    {
        var highScores = from student in students
                         where student.ExamScores[exam] > score
                         select new {Name = student.FirstName, Score = student.ExamScores[exam]};

        foreach (var item in highScores)
        {
            Console.WriteLine("{0,-15}{1}", item.Name, item.Score);
        }
    }
}

public class Program
{
    public static void Main()
    {
        StudentClass sc = new StudentClass();
        sc.QueryHighScores(1, 90);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

4. 示例(从方法返回查询)

此示例演示以返回值或out参数形式从方法返回查询。

任何查询类型必须为IEnumerable 或 IEnumerable<T>类型或其派生类型。所以,方法的返回值或out参数也应该是此类型。如果某个方法将查询具象化为 List<T>或Array类,则返回值为查询结果,而非查询本身。

class MQ
{
    // QueryMethhod1 returns a query as its value.
    IEnumerable<string> QueryMethod1(ref int[] ints)
    {
        var intsToStrings = from i in ints
                            where i > 4
                            select i.ToString();
        return intsToStrings;
    }

    // QueryMethod2 returns a query as the value of parameter returnQ.
    void QueryMethod2(ref int[] ints, out IEnumerable<string> returnQ)
    {
        var intsToStrings = from i in ints
                            where i < 4
                            select i.ToString();
        returnQ = intsToStrings;
    }

    static void Main()
    {
        MQ app = new MQ();

        int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        // QueryMethod1 returns a query as the value of the method.
        var myQuery1 = app.QueryMethod1(ref nums);

        // Query myQuery1 is executed in the following foreach loop.
        Console.WriteLine("Results of executing myQuery1:");
        // Rest the mouse pointer over myQuery1 to see its type.
        foreach (string s in myQuery1)
        {
            Console.WriteLine(s);
        }

        // You also can execute the query returned from QueryMethod1 
        // directly, without using myQuery1.
        Console.WriteLine("\nResults of executing myQuery1 directly:");
        // Rest the mouse pointer over the call to QueryMethod1 to see its
        // return type.
        foreach (string s in app.QueryMethod1(ref nums))
        {
            Console.WriteLine(s);
        }


        IEnumerable<string> myQuery2;
        // QueryMethod2 returns a query as the value of its out parameter.
        app.QueryMethod2(ref nums, out myQuery2);

        // Execute the returned query.
        Console.WriteLine("\nResults of executing myQuery2:");
        foreach (string s in myQuery2)
        {
            Console.WriteLine(s);
        }


        // You can modify a query by using query composition. A saved query
        // is nested inside a new query definition that revises the results
        // of the first query.
        myQuery1 = from item in myQuery1
                   orderby item descending
                   select item;

        // Execute the modified query.
        Console.WriteLine("\nResults of executing modified myQuery1:");
        foreach (string s in myQuery1)
        {
            Console.WriteLine(s);
        }

        // Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

在上面的示例中,第一个方法以返回值的形式返回查询,第二个以out参数的形式返回查询。

5. (示例)在内存中存储查询结果

查询就是一组如何查询及组织数据的指令。在执行查询时,需要调用对象的GetEnumerator方法,该调用在foreach循环中实现。若要不经过foreach循环来计算结果,可以通过查询变量调用 以下方法:

  • ToList<TSource>
  • ToArray<TSource>
  • ToDictionary
  • ToLookup

示例如下:

class StoreQueryResults
{
    static List<int> numbers = new List<int>() { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
    static void Main()
    {

        IEnumerable<int> queryFactorsOfFour =
            from num in numbers
            where num % 4 == 0
            select num;

        // Store the results in a new variable
        // without executing a foreach loop.
        List<int> factorsofFourList = queryFactorsOfFour.ToList();

        // Iterate the list just to prove it holds data.
        foreach (int n in factorsofFourList)
        {
            Console.WriteLine(n);
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key");
        Console.ReadKey();
    }
}

6. (示例)对查询结果进行分组

分组是LINQ最强大的功能之一。下面的示例演示如何以各种方法对数据分组:

  • 按照单个属性
  • 按照字符串属性的首字母
  • 按照计算出的数值范围
  • 按照其他表达式
  • 按照符合键值

后两个查询将其结果投影到一个新的匿名类型,该类型包含学生的名字和姓。

首先我们给出需要查询的数据和辅助方法:

public class StudentClass
{
    #region data
    protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear };
    protected class Student
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int ID { get; set; }
        public GradeLevel Year;
        public List<int> ExamScores;
    }

    protected static List<Student> students = new List<Student>
    {
        new Student {FirstName = "Terry", LastName = "Adams", ID = 120, 
            Year = GradeLevel.SecondYear, 
            ExamScores = new List<int>{ 99, 82, 81, 79}},
        new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116, 
            Year = GradeLevel.ThirdYear,
            ExamScores = new List<int>{ 99, 86, 90, 94}},
        new Student {FirstName = "Hanying", LastName = "Feng", ID = 117, 
            Year = GradeLevel.FirstYear, 
            ExamScores = new List<int>{ 93, 92, 80, 87}},
        new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114, 
            Year = GradeLevel.FourthYear,
            ExamScores = new List<int>{ 97, 89, 85, 82}},
        new Student {FirstName = "Debra", LastName = "Garcia", ID = 115, 
            Year = GradeLevel.ThirdYear, 
            ExamScores = new List<int>{ 35, 72, 91, 70}},
        new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118, 
            Year = GradeLevel.SecondYear, 
            ExamScores = new List<int>{ 92, 90, 83, 78}},
        new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113, 
            Year = GradeLevel.FirstYear, 
            ExamScores = new List<int>{ 88, 94, 65, 91}},
        new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112, 
            Year = GradeLevel.FourthYear, 
            ExamScores = new List<int>{ 75, 84, 91, 39}},
        new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111, 
            Year = GradeLevel.SecondYear, 
            ExamScores = new List<int>{ 97, 92, 81, 60}},
        new Student {FirstName = "Lance", LastName = "Tucker", ID = 119, 
            Year = GradeLevel.ThirdYear, 
            ExamScores = new List<int>{ 68, 79, 88, 92}},
        new Student {FirstName = "Michael", LastName = "Tucker", ID = 122, 
            Year = GradeLevel.FirstYear, 
            ExamScores = new List<int>{ 94, 92, 91, 91}},
        new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121,
            Year = GradeLevel.FourthYear, 
            ExamScores = new List<int>{ 96, 85, 91, 60}}
    };
    #endregion

    //Helper method, used in GroupByRange.
    protected static int GetPercentile(Student s)
    {
        double avg = s.ExamScores.Average();
        return avg > 0 ? (int)avg / 10 : 0;
    }


    public void QueryHighScores(int exam, int score)
    {
        var highScores = from student in students
                         where student.ExamScores[exam] > score
                         select new {Name = student.FirstName, Score = student.ExamScores[exam]};

        foreach (var item in highScores)
        {
            Console.WriteLine("{0,-15}{1}", item.Name, item.Score);
        }
    }
}

public class Program
{
    public static void Main()
    {
        StudentClass sc = new StudentClass();
        sc.QueryHighScores(1, 90);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

实例一:

使用单个属性对数据分组。以last name 分组,分组操作使用string类型的默认比较器

public void GroupBySingleProperty()
{
    Console.WriteLine("Group by a single property in an object:");

    // Variable queryLastNames is an IEnumerable<IGrouping<string, 
    // DataClass.Student>>. 
    var queryLastNames =
        from student in students
        group student by student.LastName into newGroup
        orderby newGroup.Key
        select newGroup;

    foreach (var nameGroup in queryLastNames)
    {
        Console.WriteLine("Key: {0}", nameGroup.Key);
        foreach (var student in nameGroup)
        {
            Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
        }
    }
}
/* Output:
    Group by a single property in an object:
    Key: Adams
            Adams, Terry
    Key: Fakhouri
            Fakhouri, Fadi
    Key: Feng
            Feng, Hanying
    Key: Garcia
            Garcia, Cesar
            Garcia, Debra
            Garcia, Hugo
    Key: Mortensen
            Mortensen, Sven
    Key: O'Donnell
            O'Donnell, Claire
    Key: Omelchenko
            Omelchenko, Svetlana
    Key: Tucker
            Tucker, Lance
            Tucker, Michael
    Key: Zabokritski
            Zabokritski, Eugene
*/

实例二:

以last name的第一个字母对数据分组

public void GroupBySubstring()
{            
    Console.WriteLine("\r\nGroup by something other than a property of the object:");

    var queryFirstLetters =
        from student in students
        group student by student.LastName[0];

    foreach (var studentGroup in queryFirstLetters)
    {
        Console.WriteLine("Key: {0}", studentGroup.Key);
        // Nested foreach is required to access group items.
        foreach (var student in studentGroup)
        {
            Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
        }
    }           
}
/* Output:
    Group by something other than a property of the object:
    Key: A
            Adams, Terry
    Key: F
            Fakhouri, Fadi
            Feng, Hanying
    Key: G
            Garcia, Cesar
            Garcia, Debra
            Garcia, Hugo
    Key: M
            Mortensen, Sven
    Key: O
            O'Donnell, Claire
            Omelchenko, Svetlana
    Key: T
            Tucker, Lance
            Tucker, Michael
    Key: Z
            Zabokritski, Eugene
*/

实例三:

通过范围数值对数据分组,将结果投影到一个匿名类型,该类型包含学生的姓名及所属百分范围。GetPercential根据学生的平均分数计算百分比。

//辅助方法,在GroupByRange中使用。
protected static int GetPercentile(Student s)
{
    double avg = s.ExamScores.Average();
    return avg > 0 ? (int)avg / 10 : 0;
}

将下面的方法粘贴到StudentClass类中,将Main方法中调用的语句改成sc.GroupByRange即可。

public void GroupByRange()
{            
    Console.WriteLine("\r\nGroup by numeric range and project into a new anonymous type:");

    var queryNumericRange =
        from student in students
        let percentile = GetPercentile(student)
        group new { student.FirstName, student.LastName } by percentile into percentGroup
        orderby percentGroup.Key
        select percentGroup;

    // Nested foreach required to iterate over groups and group items.
    foreach (var studentGroup in queryNumericRange)
    {
        Console.WriteLine("Key: {0}", (studentGroup.Key * 10));
        foreach (var item in studentGroup)
        {
            Console.WriteLine("\t{0}, {1}", item.LastName, item.FirstName);
        }
    }            
}
/* Output:
    Group by numeric range and project into a new anonymous type:
    Key: 60
            Garcia, Debra
    Key: 70
            O'Donnell, Claire
    Key: 80
            Adams, Terry
            Feng, Hanying
            Garcia, Cesar
            Garcia, Hugo
            Mortensen, Sven
            Omelchenko, Svetlana
            Tucker, Lance
            Zabokritski, Eugene
    Key: 90
            Fakhouri, Fadi
            Tucker, Michael
*/

实例四:

通过布尔表达式对数据分组。在下例中,测试学生平均成绩是否超过75,结果以匿名类型给出。

public void GroupByBoolean()
{            
    Console.WriteLine("\r\nGroup by a Boolean into two groups with string keys");
    Console.WriteLine("\"True\" and \"False\" and project into a new anonymous type:");
    var queryGroupByAverages = from student in students
                               group new { student.FirstName, student.LastName }
                                    by student.ExamScores.Average() > 75 into studentGroup
                               select studentGroup;

    foreach (var studentGroup in queryGroupByAverages)
    {
        Console.WriteLine("Key: {0}", studentGroup.Key);
        foreach (var student in studentGroup)
            Console.WriteLine("\t{0} {1}", student.FirstName, student.LastName);
    }            
}
/* Output:
    Group by a Boolean into two groups with string keys
    "True" and "False" and project into a new anonymous type:
    Key: True
            Terry Adams
            Fadi Fakhouri
            Hanying Feng
            Cesar Garcia
            Hugo Garcia
            Sven Mortensen
            Svetlana Omelchenko
            Lance Tucker
            Michael Tucker
            Eugene Zabokritski
    Key: False
            Debra Garcia
            Claire O'Donnell
*/

7. 创建嵌套组

下面演示如何在LINQ中创建嵌套组。根据学生年级分组后,再根据姓名进一步分组

public void QueryNestedGroups()
{
    var queryNestedGroups =
        from student in students
        group student by student.Year into newGroup1
        from newGroup2 in
            (from student in newGroup1
             group student by student.LastName)
        group newGroup2 by newGroup1.Key;

    // Three nested foreach loops are required to iterate 
    // over all elements of a grouped group. Hover the mouse 
    // cursor over the iteration variables to see their actual type.
    foreach (var outerGroup in queryNestedGroups)
    {
        Console.WriteLine("DataClass.Student Level = {0}", outerGroup.Key);
        foreach (var innerGroup in outerGroup)
        {
            Console.WriteLine("\tNames that begin with: {0}", innerGroup.Key);
            foreach (var innerGroupElement in innerGroup)
            {
                Console.WriteLine("\t\t{0} {1}", innerGroupElement.LastName, innerGroupElement.FirstName);
            }
        }
    }
}
/*
 Output:
DataClass.Student Level = SecondYear
        Names that begin with: Adams
                Adams Terry
        Names that begin with: Garcia
                Garcia Hugo
        Names that begin with: Omelchenko
                Omelchenko Svetlana
DataClass.Student Level = ThirdYear
        Names that begin with: Fakhouri
                Fakhouri Fadi
        Names that begin with: Garcia
                Garcia Debra
        Names that begin with: Tucker
                Tucker Lance
DataClass.Student Level = FirstYear
        Names that begin with: Feng
                Feng Hanying
        Names that begin with: Mortensen
                Mortensen Sven
        Names that begin with: Tucker
                Tucker Michael
DataClass.Student Level = FourthYear
        Names that begin with: Garcia
                Garcia Cesar
        Names that begin with: O'Donnell
                O'Donnell Claire
        Names that begin with: Zabokritski
                Zabokritski Eugene        
 */

 

posted on 2015-02-09 10:52  code_philosophy  阅读(433)  评论(0编辑  收藏  举报