在 C# 中编写 LINQ 查询

一.查询语法

编写大多数查询的推荐方式是使用查询语法创建查询表达式。
    // 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 编写所有这些查询,如下面的示例所示:
var query = from num in numbers...
二.方法语法
某些查询操作必须表示为方法调用。 最常见的此类方法是可返回单一数值的方法,例如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);
如果方法具有 Action 或 Func 参数,则这些参数以 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);

三.混合查询和方法语法

此示例演示如何对查询子句的结果使用方法语法。 只需将查询表达式括在括号中,然后应用点运算符并调用方法。
// 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();

四.查询对象的集合

public static 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($"{item.Name,-15}{item.Score}"); 
    } 
}

public class Program 
{ 
    public static void Main() 
    { 
        Student.QueryHighScores(1, 90); 
        // Keep the console window open in debug mode. 
        Console.WriteLine("Press any key to exit"); 
        Console.ReadKey(); 
    } 
}

五.如何从⽅法中返回查询

此示例演示如何以返回值和 out 参数形式从方法中返回查询。查询对象可编写,这意味着你可以从方法中返回查询。
// 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; 
}

六.在内存中存储查询结果

查询基本上是针对如何检索和组织数据的一套说明。当请求结果中的每个后续项目时,查询将延迟执行。使用foreach 循环访问结果时,项将在受到访问时返回。 若要在不执行 foreach 循环的情况下评估查询并存储其结果,只需调用查询变量上的以下方法之一:
ToList
ToArray
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. 
    Console.WriteLine(factorsofFourList[2]); 
    factorsofFourList[2] = 0; 
    Console.WriteLine(factorsofFourList[2]); 
    
    // Keep the console window open in debug mode. 
    Console.WriteLine("Press any key"); 
    Console.ReadKey(); 
    } 
}

七.对查询结果进⾏分组

分组是 LINQ 最强大的功能之一。 以下示例演示如何以各种方式对数据进行分组:
依据单个属性。
依据字符串属性的首字母。
依据计算出的数值范围。
依据布尔谓词或其他表达式。
依据组合键。
此外,最后两个查询将其结果投影到一个新的匿名类型中,该类型仅包含学生的名字和姓氏。 有关详细信息,请参阅 group 子句。
 
按单个属性分组示例
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: {nameGroup.Key}"); 
        foreach (var student in nameGroup) 
        { 
            Console.WriteLine($"\t{student.LastName}, {student.FirstName}"); 
        } 
    } 
}
按值分组示例
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: {studentGroup.Key}"); 
        // Nested foreach is required to access group items. 
        foreach (var student in studentGroup) 
        { 
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}"); 
        } 
    } 
}
按范围分组示例
以下示例演示如何通过使用某个数值范围作为分组键对源元素进行分组。
//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 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: {studentGroup.Key * 10}"); 
        // Nested foreach is required to access group items. 
        foreach (var student in studentGroup) 
        { 
            Console.WriteLine($"\t{student.LastName}, {student.FirstName}"); 
        } 
    } 
}
按比较分组示例
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: {studentGroup.Key}"); 
            foreach (var student in studentGroup) 
            Console.WriteLine($"\t{student.FirstName} {student.LastName}"); 
        }
}
按匿名类型分组
以下示例演示如何使用匿名类型来封装包含多个值的键。
public 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 {scoreGroup.Key.FirstLetter} who scored {s} 85"); 
        foreach (var item in scoreGroup) 
        { 
            Console.WriteLine($"\t{item.FirstName} {item.LastName}"); 
        } 
    } 
}
创建嵌套组
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 innerGroup in outerGroup) 
    { 
        Console.WriteLine($"DataClass.Student Level = {outerGroup.Key}");
        string s = scoreGroup.Key.Score == true ? "more than" : "less than"; 
        Console.WriteLine($"\tNames that begin with: {innerGroup.Key}");
        foreach (var innerGroupElement in innerGroup) 
        { 
            Console.WriteLine($"\t\t{innerGroupElement.LastName} {innerGroupElement.FirstName}");
        } 
    } 
}
对分组操作执⾏⼦查询
创建查询的两种不同方式,此查询将源数据排序成组,然后分别对每个组执行子查询。 每个示例中的基
本方法是使用名为 newGroup 的“接续块”对源元素进行分组,然后针对 newGroup 生成新的子查询。
public void QueryMax() {
    var queryGroupMax = 
    from student in students 
    group student by student.Year into studentGroup 
    select new 
    { 
        Level = studentGroup.Key, 
        HighestScore = 
        (from student2 in studentGroup select student2.ExamScores.Average()).Max() 
    };
    
    int count = queryGroupMax.Count();
    Console.WriteLine($"Number of groups = {count}");
    
    foreach (var item in queryGroupMax) 
    { 
        Console.WriteLine($" {item.Level} Highest Score={item.HighestScore}");
    } 
}
此外,还可以使用方法语法编写上述代码片段中的查询。 下面的代码片段具有使用方法语法编写的语义上等效的查询。
public void QueryMaxUsingMethodSyntax() {
    var queryGroupMax = students
    .GroupBy(student => student.Year)
    .Select(studentGroup => new
    select new 
    { 
        Level = studentGroup.Key,
        HighestScore = 
        (from student2 in studentGroup select student2.ExamScores.Average()).Max() 
    };
    
    int count = queryGroupMax.Count();
    Console.WriteLine($"Number of groups = {count}");
    
    foreach (var item in queryGroupMax) 
    { 
        Console.WriteLine($" {item.Level} Highest Score={item.HighestScore}");
    } 
}
八.在运⾏时动态指定谓词筛选器
在某些情况下,在运行时之前你不知道必须将多少个谓词应用于 where 子句中的源元素。 动态指定多个谓词筛选器的方法之一是使用 Contains 方法,如以下示例中所示。
使用 Contains 方法进行筛选
class DynamicPredicates : StudentClass
{
    static void Main(string[] args)
    {
        string[] ids = { "111", "114", "112" };
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
    static void QueryByID(string[] ids) {
        var queryNames = 
        from student in students 
        let i = student.ID.ToString() 
        where ids.Contains(i) 
        select new { student.LastName, student.ID };
        
        int count = queryGroupMax.Count();
        Console.WriteLine($"Number of groups = {count}");
        
        foreach (var name in queryNames) 
        { 
            Console.WriteLine($"{name.LastName}: {name.ID}");
        } 
    }
}
使用 switch 语句进行筛选
// To run this sample, first specify an integer value of 1 to 4 for the command 
// line. This number will be converted to a GradeLevel value that specifies which 
// set of students to query. 
// Call the method: QueryByYear(args[0]);

static void QueryByYear(string level)
{
    GradeLevel year = (GradeLevel)Convert.ToInt32(level); 
    IEnumerable<Student> studentQuery = null;
    switch (year) 
    { 
        case GradeLevel.FirstYear: 
            studentQuery = from student in students 
            where student.Year == GradeLevel.FirstYear 
            select student; 
            break; 
        case GradeLevel.SecondYear: 
            studentQuery = from student in students 
            where student.Year == GradeLevel.SecondYear 
            select student; 
            break; 
        case GradeLevel.ThirdYear: 
            studentQuery = from student in students 
            where student.Year == GradeLevel.ThirdYear 
            select student; 
            break; 
        case GradeLevel.FourthYear: 
            studentQuery = from student in students 
            where student.Year == GradeLevel.FourthYear 
            select student; 
            break; 
        default: 
            break; 
    }
    
    Console.WriteLine($"The following students are at level {year}"); 
    foreach (Student name in studentQuery) 
    { 
        Console.WriteLine($"{name.LastName}: {name.ID}"); 
    }
}

九.执⾏内部联接

-简单键联接
下面的示例创建两个集合,其中包含两种用户定义类型 Person 和 Pet 的对象。 查询使用 C# 中的 join 子句
将 Person 对象与 Owner 是该 Person 的 Pet 对象匹配。 C# 中的 select 子句定义结果对象的外观。 在此示
例中,结果对象是由所有者名字和宠物姓名组成的匿名类型。
class Person { 
        public string FirstName { get; set; } 
        public string LastName { get; set; } 
    }
    class Pet { 
        public string Name { get; set; } 
        public Person Owner { get; set; } 
    }
    /// <summary> 
    /// Simple inner join. 
    /// </summary>
    public static void InnerJoinExample()
    {
        Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; 
        Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; 
        Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; 
        Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; 
        Person rui = new Person { FirstName = "Rui", LastName = "Raposo" }; 
        Pet barley = new Pet { Name = "Barley", Owner = terry }; 
        Pet boots = new Pet { Name = "Boots", Owner = terry }; 
        Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; 
        Pet bluemoon = new Pet { Name = "Blue Moon", Owner = rui }; 
        Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; 
        // Create two lists.
        List<Person> people = new List<Person> { magnus, terry, charlotte, arlene, rui }; 
        List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

        // Create a collection of person-pet pairs. Each element in the collection 
        // is an anonymous type containing both the person's name and their pet's name.
        var query = 
            from person in people join pet in pets on person equals pet.Owner 
            select new { OwnerName = person.FirstName, PetName = pet.Name }; 
        foreach (var ownerAndPet in query)
        { 
            Console.WriteLine($"\"{ownerAndPet.PetName}\" is owned by {ownerAndPet.OwnerName}"); 
        }
    }
- 组合键联接
class Employee
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int EmployeeID { get; set; }
    }
    class Student
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int StudentID { get; set; }
    }
    public void CompositeKeyJoinExample()
    {
        // Create a list of employees.
        List<Employee> employees = new List<Employee>
        { 
            new Employee { FirstName = "Terry", LastName = "Adams", EmployeeID = 522459 },
            new Employee { FirstName = "Charlotte", LastName = "Weiss", EmployeeID = 204467 },
            new Employee { FirstName = "Magnus", LastName = "Hedland", EmployeeID = 866200 },
            new Employee { FirstName = "Vernette", LastName = "Price", EmployeeID = 437139 } };
        // Create a list of students.
        List<Student> students = new List<Student> { 
            new Student { FirstName = "Vernette", LastName = "Price", StudentID = 9562 },
            new Student { FirstName = "Terry", LastName = "Earls", StudentID = 9870 }, 
            new Student { FirstName = "Terry", LastName = "Adams", StudentID = 9913 } };
        // Join the two data sources based on a composite key consisting of first and last name, 
        // to determine which employees are also students.
        IEnumerable<string> query = from employee in employees 
                                    join student in students on new 
                                    { employee.FirstName, employee.LastName } 
                                    equals new { student.FirstName, student.LastName } 
                                    select employee.FirstName + " " + employee.LastName; 
        Console.WriteLine("The following people are both employees and students:"); 
        foreach (string name in query) Console.WriteLine(name);
    }
- 多联接
  var query = from person in people 
                    join cat in cats on person equals cat.Owner 
                    join dog in dogs on new { Owner = person, Letter = cat.Name.Substring(0, 1) } 
                    equals new { dog.Owner, Letter = dog.Name.Substring(0, 1) } 
                    select new { CatName = cat.Name, DogName = dog.Name };
- 使用分组联接的内联
     // Create a list of students.
        // Create two lists.
        List<Person> people = new List<Person> { magnus, terry, charlotte, arlene }; 
        List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy }; 
        var query1 = from person in people 
                     join pet in pets on person equals pet.Owner 
                     into gj from subpet in gj 
                     select new { OwnerName = person.FirstName, PetName = subpet.Name };
- 分组联接
 var query = from person in people join pet in pets on person 
                    equals pet.Owner into gj 
                    select new { OwnerName = person.FirstName, Pets = gj };
- 用于创建 XML 的分组联接
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; 
        Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; 
        Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; 
        Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; 
        Pet barley = new Pet { Name = "Barley", Owner = terry }; 
        Pet boots = new Pet { Name = "Boots", Owner = terry }; 
        Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; 
        Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry }; 
        Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

        // Create two lists.
        List<Person> people = new List<Person> { magnus, terry, charlotte, arlene }; 
        List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };
        // Create XML to display the hierarchical organization of people and their pets.
        XElement ownersAndPets = new XElement("PetOwners",
            from person in people 
            join pet in pets on person equals pet.Owner into gj 
            select new XElement("Person", 
            new XAttribute("FirstName", person.FirstName), 
            new XAttribute("LastName", person.LastName), 
            from subpet in gj 
            select new XElement("Pet", subpet.Name))); 
        Console.WriteLine(ownersAndPets);

 在查询表达式中处理 null 值

var query1 = from c in categories
                     where c != null
                     join p in products on c.ID equals p?.CategoryID
                     select new { Category = c.Name, Name = p.Name };

var query = from o in db.Orders
                    join e in db.Employees on o.EmployeeID equals (int?)e.EmployeeID
                    select new { o.OrderID, e.FirstName };
在查询表达式中处理异常
    class ExceptionsOutsideQuery
    {
        static void Main()
        {
            // DO THIS with a datasource that might 
            // throw an exception. It is easier to deal with 
            // outside of the query expression.
            IEnumerable<int> dataSource; 
            try 
            { 
                dataSource = GetData(); 
            }
            catch (InvalidOperationException)
            {
                // Handle (or don't handle) the exception 
                // in the way that is appropriate for your application.
                Console.WriteLine("Invalid operation"); 
                goto Exit;
            }
            // If we get here, it is safe to proceed.
            var query = 
                from i in dataSource 
                        select i * i; 
            foreach (var i in query)
                Console.WriteLine(i.ToString());
            //Keep the console window open in debug mode
            Exit: 
            Console.WriteLine("Press any key to exit"); 
            Console.ReadKey();

            // A data source that is very likely to throw an exception! static
            IEnumerable<int> GetData() 
            { 
                throw new InvalidOperationException(); 
            }
        }
    }

 

posted @ 2022-04-12 15:13  Tammytan  阅读(345)  评论(0编辑  收藏  举报