语言集成查询 (LINQ) 是一组技术的名称,这些技术建立在将查询功能直接集成到 C# 语言(以及 Visual Basic 和可能的任何其他 .NET 语言)的基础上。借助于 LINQ,查询现在已是高级语言构造,就如同类、方法、事件等等。
对于编写查询的开发人员来说,LINQ 最明显的“语言集成”部分是查询表达式。查询表达式是使用 C# 3.0 中引入的声明性查询语法编写的。通过使用查询语法,您甚至可以使用最少的代码对数据源执行复杂的筛选、排序和分组操作。您使用相同的基本查询表达式模式来查询和转换 SQL 数据库、ADO.NET 数据集、XML 文档和流以及 .NET 集合中的数据。
下面的示例演示了完整的查询操作。完整操作包括创建数据源、定义查询表达式,以及在 foreach 语句中执行查询。
{
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
本主题演示在 C# 中编写 LINQ 查询的三种方式:
-
使用查询语法。
-
使用方法语法。
-
组合使用查询语法和方法语法。
下面的示例使用前面列出的每种方式演示一些简单的 LINQ 查询。一般的规则是尽可能使用 (1),而在必要时使用 (2) 和 (3)。
说明: |
---|
这些查询作用于简单的内存中集合;但是,基本语法与 LINQ to SQL 和 LINQ to XML 中使用的语法相同。 |
查询语法
编写大多数查询的推荐方式是使用查询语法来创建查询表达式。下面的示例演示了三个查询表达式。第一个查询表达式演示如何通过用 where 子句应用条件来筛选或限制结果,它返回源序列中值大于 7 或小于 3 的所有元素。第二个表达式演示如何对返回的结果进行排序。第三个表达式演示如何按照键对结果进行分组,此查询可根据单词的第一个字母返回两个组。
// 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<(Of <(T>)>)。所有这些查询都可以使用 var 编写,如下面的示例所示:
var query = from num in numbers...
在上述每个示例中,直到您在 foreach 语句中循环访问查询变量时,查询才会实际执行。有关更多信息,请参见 LINQ 查询介绍。
方法语法
某些查询操作必须表示为方法调用。最常见的此类方法是那些返回单一数值的方法,如 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<(Of <(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);
混合的查询和方法语法
此示例演示如何对查询子句的结果使用方法语法。只需将查询表达式括在括号内,然后应用点运算符并调用此方法。在下面的示例中,查询 7 返回其值在 3 和 7 之间的数字个数。但是,通常更好的做法是使用另一个变量来存储方法调用的结果。这样就不太容易将查询本身与查询结果相混淆。
// 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 返回单个值而不是一个集合,因此该查询立即执行。
上述查询可以通过结合使用隐式类型化与 var 进行编写,如下所示:
var numCount = (from num in numbers...
它可以按如下方式使用方法语法进行编写:
var numCount = numbers.Where(n => n < 3 || n > 7).Count();
它可以按如下方式使用显式类型化进行编写:
int numCount = numbers.Where(n => n < 3 || n > 7).Count();
如何:以各种方式对结果进行分组
如何:以各种方式对结果进行分组(C# 编程指南)
更新:2007 年 11 月
分组是 LINQ 最强大的功能之一。下面的示例演示如何以各种方式对数据进行分组:
按照单个属性。
按照字符串属性的首字母。
按照计算出的数值范围。
按照布尔谓词或其他表达式。
按照复合键。
此外,最后两个查询将它们的结果投影到一个新的匿名类型中,该类型仅包含学生的名字和姓氏。有关更多信息,请参见 group 子句(C# 参考)。
示例本主题中的所有示例都使用下列帮助器类和数据源。
C#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 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(); } }下面的示例演示如何通过使用元素的单个属性作为组键对源元素进行分组。在这种情况下,该键为 string。还可以使用子字符串作为键。分组操作将对该类型使用默认的相等比较器。
C#private static void GroupBySingleProperty() { Console.WriteLine("Group by a single property in an object"); // queryLastNames is an IEnumerable<IGrouping<string, DataClass.Student>> // var is easier to type. 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: Feng Feng, Hanying Key: Garcia Garcia, Hugo Garcia, Cesar Garcia, Debra Key: Mortensen Mortensen, Sven Key: O'Donnell O'Donnell, Claire Key: Omelchenko Omelchenko, Svetlana Key: Tucker Tucker, Michael Tucker, Lance */下面的示例演示如何通过使用除对象属性以外的某个项作为组键对源元素进行分组。
C#private static 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 first character: Key: O Omelchenko, Svetlana O'Donnell, Claire Key: G Garcia, Hugo Garcia, Cesar Garcia, Debra Key: M Mortensen, Sven Key: T Tucker, Michael Tucker, Lance Key: F Feng, Hanying */下面的示例演示如何通过使用某个数值范围作为组键对源元素进行分组。然后,查询将结果投影到一个匿名类型中,该类型仅包含学生的名字和姓氏以及该学生所属的百分点范围。使用匿名类型的原因是没有必要使用完整的 Student 对象来显示结果。GetPercentile 是一个帮助器函数,它根据学生的平均得分计算百分点:
C#static int GetPercentile(Student s) { double avg = s.Scores.Average(); return avg > 0 ? (int)avg / 10 : 0; }C#private static 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 Omelchenko, Svetlana O'Donnell, Claire Key: 80 Garcia, Hugo Mortensen, Sven Garcia, Cesar Feng, Hanying Tucker, Lance Key: 90 Tucker, Michael */下面的示例演示如何通过使用布尔比较表达式对源元素进行分组。与上述示例一样,结果被投影到一个匿名类型中,因为不需要完整的源元素。请注意,在执行查询时,该匿名类型中的属性将变成 Key 成员上的属性,并且可以通过名称进行访问。
C#private static 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 Svetlana Omelchenko Hugo Garcia Sven Mortensen Michael Tucker Cesar Garcia Hanying Feng Lance Tucker Key: False Claire O'Donnell Debra Garcia */下面的示例演示如何使用匿名类型来封装包含多个值的键。在这种情况下,第二个键值是一个布尔值,它指定该学生在第一次考试中的得分是否超过了 85 分。可以按照该键中的任何属性对多组值进行排序。
C#private static void GroupByCompositeKey() { var queryHighScoreGroups = from student in students group student by new { FirstLetter = student.LastName[0], Score = student.ExamScores[0] > 85 } into studentGroup orderby studentGroup.Key.FirstLetter select studentGroup; Console.WriteLine("/r/nGroup and order by a compound key:"); foreach (var scoreGroup in queryHighScoreGroups) { string s = scoreGroup.Key.Score == true ? "more than" : "less than"; Console.WriteLine("Name starts with {0} who scored {1} 85", scoreGroup.Key.FirstLetter, s); foreach (var item in scoreGroup) { Console.WriteLine("/t{0} {1}", item.FirstName, item.LastName); } } } /* Output: Group and order by a compound key: Name starts with F who scored more than 85 Hanying Feng Name starts with G who scored more than 85 Hugo Garcia Cesar Garcia Name starts with G who scored less than 85 Debra Garcia Name starts with M who scored more than 85 Sven Mortensen Name starts with O who scored more than 85 Svetlana Omelchenko Name starts with O who scored less than 85 Claire O'Donnell Name starts with T who scored more than 85 Michael Tucker Name starts with T who scored less than 85 Lance Tucker */编译代码此示例包含对如何:查询对象集合(C# 编程指南)内示例应用程序中定义的对象的引用。若要编译和运行此方法,请将它粘贴到该应用程序的 StudentClass 类中,并且在 Main 方法中添加对它的调用。
在改写此方法以适合您自己的应用程序时,请记住 LINQ 需要 .NET Framework 3.5 版,并且项目必须包含一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。LINQ to SQL、LINQ to XML 和 LINQ to DataSet 类型需要