【深入浅出C#】章节 9: C#高级主题:LINQ查询和表达式

C#高级主题涉及到更复杂、更灵活的编程概念和技术,能够让开发者更好地应对现代软件开发中的挑战。其中,LINQ查询和表达式是C#高级主题中的一项关键内容,具有以下重要性和优势:

  1. 数据处理和操作: 在现代软件中,数据处理和操作是至关重要的。LINQ提供了一种统一的方式来查询和操作各种类型的数据,包括集合、数据库、XML等。这使得数据的处理变得更加直观和便捷。
  2. 简洁的语法: LINQ引入了类似SQL的查询语法,使得开发者能够以更简洁的方式表达数据查询和操作。这种直观的语法让代码更易于理解和维护。
  3. 类型安全: LINQ是在编译时进行类型检查的,这意味着编译器可以在编译阶段捕获类型错误,减少了运行时错误的可能性。
  4. 提升生产力: 使用LINQ可以在短时间内完成复杂的数据查询和转换操作,从而提高开发效率。开发者不再需要编写大量的循环和临时变量来处理数据。
  5. 适用于多种数据源: LINQ不仅适用于集合数据,还可以应用于数据库查询、XML处理等各种数据源,为不同领域的开发提供了一致的数据处理方法。
  6. 更少的错误: LINQ可以帮助开发者避免一些常见的编程错误,如越界、空引用等。它的语法和方法可以帮助开发者更好地处理边界情况。
  7. 可读性强: LINQ的查询语法非常直观,使得代码更易于理解和维护。这对于团队合作和项目维护至关重要。
  8. 适应复杂需求: 在处理一些复杂的数据需求时,LINQ的强大功能可以帮助开发者编写出更具逻辑性的代码,更好地表达业务逻辑。

一、LINQ概述

1.1 LINQ(Language Integrated Query)的定义和背景

LINQ(Language Integrated Query)是一种用于.NET Framework的编程模型,旨在将数据查询与编程语言集成在一起。它的背景和定义如下:
背景: 在过去,对于不同类型的数据,开发者需要使用不同的语法和API进行查询和操作。例如,在关系数据库中,需要使用SQL进行查询,而在.NET中,需要使用各种不同的API来操作集合、XML等。这种情况下,代码变得分散,难以维护,而且需要学习多种查询语言。
定义: LINQ解决了上述问题,它是一种在编程语言中集成查询的方式。通过LINQ,开发者可以使用统一的语法在.NET语言(如C#)中执行查询操作,而无需了解底层的数据源类型和查询方式。无论是对集合、数据库、XML还是其他数据源,都可以使用类似的语法来进行查询和操作。
LINQ的主要目标是提供一种统一的查询体验,让开发者能够在编程语言中以更直观、灵活的方式来处理数据。这不仅提高了开发效率,还使代码更具可读性和可维护性。同时,由于LINQ是在编译时进行类型检查的,它也能够减少运行时错误。

1.2 LINQ的特点和用途

LINQ(Language Integrated Query)具有以下特点和用途:

  1. 统一的语法: LINQ提供了统一的查询语法,无论是查询集合、数据库、XML还是其他数据源,都可以使用类似的语法进行查询和操作,减少了学习成本和代码的复杂性。
  2. 编译时类型检查: LINQ在编译时进行类型检查,这意味着在代码编写阶段就能发现错误,减少了运行时错误的可能性。
  3. 延迟加载: LINQ使用延迟加载(Deferred Execution)机制,只有在需要查询结果时才会执行实际的查询,从而优化性能。
  4. 强大的查询能力: LINQ提供了丰富的查询操作符和方法,可以进行过滤、排序、投影、分组等多种查询操作。
  5. 面向对象查询: LINQ是面向对象的,可以对对象进行查询,而不仅限于关系数据库。
  6. 查询与代码融合: LINQ查询表达式和代码混合编写,使查询与业务逻辑融为一体,提高了代码的可读性。
  7. 适用范围广泛: LINQ不仅适用于关系型数据库,还可以用于集合、XML、对象等多种数据源。
  8. 集成性: LINQ与.NET语言(如C#)紧密集成,不需要额外学习新的查询语言。
  9. 支持扩展: 可以通过自定义扩展方法来为LINQ添加自定义查询操作。

LINQ的用途包括但不限于:

  • 数据库查询:可以用LINQ查询关系型数据库,代替传统的SQL查询。
  • 集合操作:可以对集合进行过滤、排序、分组等操作,替代传统的循环遍历。
  • XML处理:可以通过LINQ查询和操作XML文档,使XML处理更加简洁。
  • 对象查询:可以对对象集合进行查询,用于业务逻辑处理。
  • 数据转换:可以将一种数据形式转换为另一种,如将数据库结果转换为对象集合。
1.3 LINQ查询和表达式的基本工作原理

LINQ(Language Integrated Query)查询和表达式的基本工作原理如下:

  1. 查询表达式的转换: 当你使用LINQ查询语法时,编译器会将这些查询表达式转换为标准的扩展方法调用。这些扩展方法属于LINQ标准查询运算符集合,它们可以在System.Linq命名空间中找到。
  2. 延迟执行: LINQ查询采用了延迟执行的概念,这意味着查询表达式并不会立即执行查询操作,而是在实际需要查询结果时才会执行。这有助于提高性能,因为只有在需要时才会访问数据源。
  3. 查询翻译: 当查询被执行时,LINQ提供程序会将LINQ查询转换为特定数据源(如集合、数据库、XML等)的查询语言这意味着无论数据源是什么,LINQ查询的语法都是一致的。
  4. 优化和提升: LINQ提供程序会尝试对查询进行优化,以提高查询性能。这可能包括筛选和投影操作的优化,以及在数据库查询中生成最优化的SQL查询语句。
  5. 编译时类型检查: LINQ查询在编译时进行类型检查,这可以帮助在编译期间捕获错误,避免在运行时发生类型错误。
  6. 返回结果: 最终,LINQ查询会返回一个结果集,该结果集可以是一个集合、一个单一的值或其他形式,取决于查询的目的和数据源。

二、LINQ基础

2.1 LINQ查询的语法和结构
  1. 查询表达式语法:
    查询表达式使用类似于SQL的语法来编写查询。以下是一些常见的查询表达式关键字和示例:
  • from: 指定数据源和范围变量。
  • where: 用于过滤数据。
  • orderby: 用于排序数据。
  • select: 用于投影数据,选择要返回的数据部分。
  • group: 用于分组数据。
  • join: 用于连接两个数据源。
  • into: 用于将一个查询的结果引入到另一个查询中。

以下是一个使用查询表达式语法的示例,从一个整数列表中选择偶数并按升序排序:

var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var query = from num in numbers
            where num % 2 == 0
            orderby num ascending
            select num;

foreach (var num in query)
{
    Console.WriteLine(num);
}
  1. 方法语法:
    方法语法使用扩展方法来实现查询。它更加紧凑,可以在方法链中连续调用多个操作。以下是一些常见的LINQ方法:
  • Where: 用于过滤数据。
  • OrderBy / OrderByDescending: 用于排序数据。
  • Select: 用于投影数据。
  • GroupBy: 用于分组数据。
  • Join: 用于连接两个数据源。
  • ToList / ToArray: 将查询结果转换为列表或数组。
  • Count: 返回元素数量。
  • First / FirstOrDefault: 返回第一个元素。
  • Single / SingleOrDefault: 返回单个元素。
  • Aggregate: 对序列中的元素执行累积操作。

以下是一个使用方法语法的示例,从一个字符串列表中选择长度大于3的字符串并按长度升序排序:

var strings = new List<string> { "apple", "banana", "grape", "orange", "kiwi" };

var query = strings
    .Where(str => str.Length > 3)
    .OrderBy(str => str.Length)
    .ToList();

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

无论是查询表达式还是方法语法,都是LINQ的两种不同的写法,用于在.NET应用程序中进行数据查询和操作。选择哪种语法主要取决于个人偏好和具体的使用场景。

2.2 查询语法和方法语法的对比

LINQ提供了两种不同的语法风格,即查询表达式语法和方法语法,用于执行数据查询和操作。下面是这两种语法风格的对比:
查询表达式语法:

  • 使用类似于SQL的语法,更接近自然语言。
  • 更具可读性,对于初学者来说更容易理解。
  • 通常适用于复杂查询,涉及多个条件、排序和分组。
  • 适用于查询的代码较长,多个操作可以在一个语句中组合。
  • 支持的操作有限,但足够应对常见的查询需求。
  • 可以在一定程度上提高代码的可读性,特别是对于复杂查询。
var query = from person in people
            where person.Age > 18
            orderby person.LastName
            select person.FirstName;

方法语法:

  • 使用链式调用的扩展方法,更紧凑、简洁。
  • 适用于简单查询,特别是只涉及筛选、排序和投影等基本操作。
  • 可以在代码中进行更细粒度的操作控制,适用于灵活的操作序列。
  • 操作更加灵活,可以根据需求组合不同的方法调用。
  • 支持的操作更丰富,因为可以调用LINQ扩展方法。
  • 适用于需要更大程度的代码控制和性能优化的情况。
var query = people
    .Where(person => person.Age > 18)
    .OrderBy(person => person.LastName)
    .Select(person => person.FirstName);

查询表达式语法更适合对于可读性要求较高的查询,而方法语法更适合需要更多灵活性和性能优化的情况。在实际开发中,可以根据具体情况来选择最合适的语法风格。同时,两种语法是等价的,可以相互转换,因此也可以根据具体需求在两种风格之间切换。

2.3 基本LINQ查询操作符介绍

LINQ提供了一系列基本的查询操作符,用于从各种数据源(例如集合、数据库、XML等)中进行数据查询和操作。这些操作符允许你筛选、排序、投影、分组等。以下是一些常用的基本LINQ查询操作符的介绍:

  1. Where: 用于根据指定的条件筛选元素。只返回满足条件的元素。
var result = collection.Where(item => item.Property > 5);
  1. OrderBy / OrderByDescending: 用于对元素进行升序或降序排序。
var result = collection.OrderBy(item => item.Property);
  1. Select: 用于投影数据,选择元素的特定属性或执行转换操作。
var result = collection.Select(item => item.Property);
  1. GroupBy: 用于将元素分组,通常配合聚合函数使用。
var result = collection.GroupBy(item => item.Category);
  1. Join: 用于将两个数据源中的元素连接起来,根据共同的键进行连接。
var result = collection1.Join(collection2, item1 => item1.Key, item2 => item2.Key, (item1, item2) => new { item1, item2 });
  1. Distinct: 用于去除重复的元素。
var result = collection.Distinct();
  1. Take / Skip: 用于从序列中获取前N个元素或跳过前N个元素。
var result = collection.Take(5);
var result = collection.Skip(3);
  1. First / FirstOrDefault: 用于获取序列中的第一个元素。
var result = collection.First();
var result = collection.FirstOrDefault();
  1. Single / SingleOrDefault: 用于获取序列中的单个元素。
var result = collection.Single(item => item.Property == value);
var result = collection.SingleOrDefault(item => item.Property == value);
  1. Any: 用于检查序列中是否存在满足条件的元素。
bool hasItems = collection.Any(item => item.Property > 5);
  1. All: 用于检查序列中的所有元素是否都满足条件。
bool allMatch = collection.All(item => item.Property > 0);
  1. Count: 返回序列中满足条件的元素数量。
int count = collection.Count(item => item.Property > 5);
  1. Sum / Average: 用于计算序列中元素的总和或平均值。
var sum = collection.Sum(item => item.Value);
var average = collection.Average(item => item.Value);

三、LINQ查询的数据源

3.1 LINQ查询的数据源类型
  1. IEnumerable:这是最常见的数据源类型,表示一个可枚举的集合,例如数组、列表、集等。
  2. IQueryable:这代表了一个可查询的数据源,通常用于与数据库查询交互。它支持延迟加载,这意味着查询不会立即执行,而是在需要结果时才会被执行,从而优化查询性能。
  3. Array:C#中的数组可以直接用于LINQ查询。
  4. List:List是一种常见的集合类型,也可以用于LINQ查询。
  5. Dictionary:字典可以用于根据键进行查询。
  6. DataSet / DataTable:这些用于处理数据库中的表格数据,可以通过LINQ查询进行筛选、排序和投影等操作。
  7. XML:LINQ to XML允许你以类似于LINQ查询的方式来处理XML数据。
  8. Entity Framework:Entity Framework是一种ORM(Object-Relational Mapping)工具,可以将数据库中的表映射为.NET对象,并且支持使用LINQ查询来操作数据库。
  9. LINQ to SQL:类似于Entity Framework,用于在数据库中执行LINQ查询。
  10. LINQ to Objects:这是针对.NET中的对象集合的标准LINQ提供程序,适用于各种集合类型。
  11. LINQ to Entities:用于将实体数据模型与数据库连接起来,支持使用LINQ查询进行数据库操作。
  12. LINQ to XML:用于处理XML数据,支持以LINQ方式查询和操作XML文档。
  13. LINQ to JSON:用于处理JSON数据,支持以LINQ方式查询和操作JSON数据。
  14. Parallel LINQ (PLINQ):这是一种支持并行执行的LINQ扩展,适用于在多核处理器上执行查询。
3.2 如何创建和准备LINQ查询的数据源

创建和准备LINQ查询的数据源涉及从各种数据类型中获取数据,然后将其转换为适用于LINQ的数据类型,例如IEnumerable、IQueryable等。下面是一些常见的方法来创建和准备LINQ查询的数据源:

  1. 使用集合类型

    • 使用数组:T[] array = new T[] { ... };
    • 使用ListList list = new List { ... };
    • 使用DictionaryDictionary dict = new Dictionary { ... };
  2. 使用LINQ提供的数据源

    • Enumerable.Range: 创建一个整数范围的序列。
      var numbers = Enumerable.Range(1, 10); // 创建从1到10的整数序列
      
    • Enumerable.Repeat: 创建一个重复值的序列。
      var repeatedValues = Enumerable.Repeat("Hello", 5); // 创建包含5个"Hello"的序列
      
  3. 使用LINQ to XML:使用LINQ查询从XML文档中提取数据。

    XDocument xmlDocument = XDocument.Load("data.xml");
    var data = from element in xmlDocument.Root.Elements("item")
               select new {
                   Name = element.Element("name").Value,
                   Price = decimal.Parse(element.Element("price").Value)
               };
    
  4. 使用LINQ to Entities / LINQ to SQL:使用ORM工具(如Entity Framework或LINQ to SQL)从数据库中获取数据。

    var dbContext = new MyDbContext();
    var data = from product in dbContext.Products
               where product.Category == "Electronics"
               select product;
    
  5. 从文件或外部数据源读取数据

    • 从文本文件中读取数据:使用File类读取文本文件中的数据。
    • 从CSV文件中读取数据:使用开源库(如CsvHelper)将CSV文件中的数据转化为对象。
    • 从数据库中读取数据:使用ADO.NET或ORM工具获取数据库中的数据。
  6. 创建自定义数据源:你可以实现自己的集合类或数据提供程序,使其支持LINQ查询。

四、LINQ查询操作和结果

4.1 如何构建和组合多个LINQ查询操作符

构建和组合多个LINQ查询操作符是通过链式调用操作符的方式来实现的。你可以在一个LINQ查询中使用多个操作符,以便对数据进行复杂的查询、过滤、投影和操作。以下是如何构建和组合多个LINQ查询操作符的示例:
假设我们有一个包含一些人员信息的集合,每个人员都有姓名、年龄和职业属性。我们想要从这个集合中选择年龄大于18的人员,并按照年龄升序排列,然后仅选择他们的姓名和职业信息。

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Occupation { get; set; }
}

// 假设已经有一个包含人员信息的集合 people
List<Person> people = new List<Person>
{
    new Person { Name = "Alice", Age = 25, Occupation = "Engineer" },
    new Person { Name = "Bob", Age = 30, Occupation = "Teacher" },
    new Person { Name = "Charlie", Age = 22, Occupation = "Student" }
};

// 构建并组合多个操作符
var query = people
    .Where(person => person.Age > 18)  // 筛选年龄大于18的人员
    .OrderBy(person => person.Age)      // 按照年龄升序排列
    .Select(person => new              // 选择姓名和职业信息
    {
        person.Name,
        person.Occupation
    });

// 执行查询并遍历结果
foreach (var personInfo in query)
{
    Console.WriteLine($"Name: {personInfo.Name}, Occupation: {personInfo.Occupation}");
}

在上面的示例中,我们使用了 WhereOrderBySelect 操作符来构建查询链。注意,每个操作符返回一个新的查询对象,使你可以继续在其上面调用其他操作符。最后,通过 foreach 循环遍历查询结果并输出。

4.2 查询操作符的返回类型和结果处理

LINQ查询操作符返回的类型取决于操作符本身以及操作前的数据源类型。不同的操作符可能返回不同类型的序列或单个元素。以下是一些常见的LINQ查询操作符的返回类型以及如何处理查询结果:

  1. Where:返回与条件匹配的元素序列。
    IEnumerable<TSource> result = collection.Where(item => item.Property > 5);
    
  2. OrderBy / OrderByDescending:返回按指定顺序排序的元素序列。
    IOrderedEnumerable<TSource> result = collection.OrderBy(item => item.Property);
    
  3. Select:返回一个包含指定投影的序列。
    IEnumerable<TResult> result = collection.Select(item => item.Property);
    
  4. GroupBy:返回一个分组后的序列,每个分组包含一个键和对应的元素序列。
    IEnumerable<IGrouping<TKey, TSource>> result = collection.GroupBy(item => item.Category);
    
  5. Join:返回连接后的序列,其中每个元素是两个数据源的匹配项。
    IEnumerable<TResult> result = collection1.Join(collection2, item1 => item1.Key, item2 => item2.Key, (item1, item2) => new { item1, item2 });
    
  6. Distinct:返回去重后的元素序列。
    IEnumerable<TSource> result = collection.Distinct();
    
  7. Take / Skip:返回指定数量的前N个元素或跳过前N个元素后的元素序列。
    IEnumerable<TSource> result = collection.Take(5);
    IEnumerable<TSource> result = collection.Skip(3);
    
  8. First / FirstOrDefault:返回序列中的第一个元素。
    TSource result = collection.First();
    TSource result = collection.FirstOrDefault();
    
  9. Single / SingleOrDefault:返回序列中的单个元素。
    TSource result = collection.Single(item => item.Property == value);
    TSource result = collection.SingleOrDefault(item => item.Property == value);
    
  10. Any:返回一个布尔值,指示序列是否包含满足条件的元素。
    bool result = collection.Any(item => item.Property > 5);
    
  11. Count:返回满足条件的元素数量。
    int count = collection.Count(item => item.Property > 5);
    
  12. Sum / Average:返回序列中元素的总和或平均值。
    decimal sum = collection.Sum(item => item.Value);
    double average = collection.Average(item => item.Value);
    

根据查询操作符的返回类型,你可以选择不同的方式来处理查询结果:

  • 对于返回序列的操作符,你可以使用循环(如foreach)来遍历结果,并处理每个元素。
  • 对于返回单个元素的操作符,你可以将结果存储在变量中,然后进一步进行处理。
  • 如果你需要将结果转换为列表或数组,可以使用ToList()ToArray()方法。
4.3 延迟执行和立即执行的区别

在LINQ中,查询操作可以分为延迟执行(Deferred Execution)和立即执行(Immediate Execution)两种方式。这两种执行方式的主要区别在于查询何时被执行以及返回的结果类型。

  1. 延迟执行(Deferred Execution)
    延迟执行意味着查询不会立即执行,而是在实际需要查询结果时才会被执行。这使得你可以构建复杂的查询链,然后在需要的时候才执行查询。延迟执行的特点包括:
  • 查询的定义和组合发生在查询链被创建的时候,但查询本身不会执行。
  • 查询的执行被推迟到访问查询结果的时候,如在循环中遍历查询结果或调用终结操作符(如ToList()ToArray()等)时。
  • 当查询被执行时,它会使用最新的数据源进行计算,而不是在查询链创建时的数据源。
  • 可以用于在遍历大量数据时优化性能,只计算和返回必要的数据。
var query = collection.Where(item => item.Property > 5); // 定义查询
foreach (var item in query) // 在循环中执行查询
{
    Console.WriteLine(item);
}
  1. 立即执行(Immediate Execution)
    立即执行意味着查询会在定义的同时立即执行,并返回查询结果。这意味着查询的计算是立即完成的,而不是在后续的代码中进行。
  • 查询的结果是实际的数据而不是查询的表达式。
  • 查询操作符被立即调用,数据被检索和处理,结果返回到变量中。
  • 适用于当你希望立即获取查询结果并处理数据时。
var result = collection.Where(item => item.Property > 5).ToList(); // 立即执行查询并获取结果

要理解哪种执行方式被使用,需要查看特定操作符的定义以及在查询链中的位置。通常,操作符的类型和使用的终结操作符(如ToList()ToArray()First()等)会决定查询的执行方式。掌握延迟执行和立即执行的区别可以帮助你更好地优化查询性能并避免不必要的计算。

五、LINQ与匿名类型

5.1 使用匿名类型处理查询结果

在LINQ中,匿名类型是一种临时的、只在查询中使用的类型,用于存储查询结果的部分或全部数据。使用匿名类型可以方便地选择要返回的属性,并且无需显式定义一个类。以下是如何使用匿名类型处理查询结果的示例:
假设我们有一个包含人员信息的集合,每个人员都有姓名、年龄和职业属性。我们想要从这个集合中选择年龄大于18的人员,并返回他们的姓名和职业信息。

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Occupation { get; set; }
}

List<Person> people = new List<Person>
{
    new Person { Name = "Alice", Age = 25, Occupation = "Engineer" },
    new Person { Name = "Bob", Age = 30, Occupation = "Teacher" },
    new Person { Name = "Charlie", Age = 22, Occupation = "Student" }
};

// 使用匿名类型选择要返回的属性
var query = from person in people
            where person.Age > 18
            select new { person.Name, person.Occupation };

foreach (var personInfo in query)
{
    Console.WriteLine($"Name: {personInfo.Name}, Occupation: {personInfo.Occupation}");
}

在上面的示例中,我们使用select new { ... }语句创建了一个匿名类型,只选择了要返回的属性。匿名类型的属性名是从查询结果中的属性名推断出来的。然后我们在foreach循环中遍历查询结果并输出。
注意以下关键点:

  • 匿名类型的类型名是由编译器生成的,并且在编译时是不可见的。
  • 每次创建匿名类型的实例时,实际上都创建了一个新的类,其属性名和类型都与查询结果的属性相匹配。
  • 由于匿名类型是临时的,所以它只能在查询范围内使用,无法将其传递到方法之外。
  • 匿名类型的属性是只读的,无法修改其值。

Tip:使用匿名类型能够使代码更简洁,并且无需显式定义类,适用于临时处理查询结果的情况。

六、LINQ和集合类型

6.1 如何在LINQ查询中处理集合类型

在LINQ查询中处理集合类型是非常常见的情况,因为LINQ的主要目的之一就是对集合进行查询、过滤、投影和操作。以下是一些常见的在LINQ查询中处理集合类型的示例:

  1. 过滤数据(Where)
    使用 Where 操作符来过滤集合中的元素,只保留满足条件的元素。
var result = collection.Where(item => item.Property > 5);
  1. 排序数据(OrderBy、OrderByDescending)
    使用 OrderByOrderByDescending 操作符对集合元素进行升序或降序排序。
var result = collection.OrderBy(item => item.Property);
  1. 投影数据(Select)
    使用 Select 操作符从集合中选择特定属性或执行转换操作。
var result = collection.Select(item => item.Property);
  1. 分组数据(GroupBy)
    使用 GroupBy 操作符将集合元素按照特定属性分组。
var result = collection.GroupBy(item => item.Category);
  1. 连接数据(Join)
    使用 Join 操作符将两个集合中的元素根据共同的键连接起来。
var result = collection1.Join(collection2, item1 => item1.Key, item2 => item2.Key, (item1, item2) => new { item1, item2 });
  1. 去重数据(Distinct)
    使用 Distinct 操作符去除集合中的重复元素。
var result = collection.Distinct();
  1. 获取前N个元素(Take)
    使用 Take 操作符获取集合中的前 N 个元素。
var result = collection.Take(5);
  1. 跳过前N个元素(Skip)
    使用 Skip 操作符跳过集合中的前 N 个元素。
var result = collection.Skip(3);
6.2 使用LINQ对集合进行过滤、映射和排序

使用LINQ对集合进行过滤、映射和排序非常简单,只需使用LINQ的相应操作符即可。下面是针对一个包含学生信息的集合,演示如何使用LINQ对集合进行过滤、映射和排序的示例:

class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
    public double GPA { get; set; }
}

List<Student> students = new List<Student>
{
    new Student { Name = "Alice", Age = 20, GPA = 3.5 },
    new Student { Name = "Bob", Age = 22, GPA = 3.2 },
    new Student { Name = "Charlie", Age = 19, GPA = 3.8 }
};

// 过滤:选择年龄小于 22 的学生
var filteredStudents = students.Where(student => student.Age < 22);

// 映射:仅选择学生的姓名和GPA信息
var mappedStudents = students.Select(student => new { student.Name, student.GPA });

// 排序:按照GPA降序排列学生
var sortedStudents = students.OrderByDescending(student => student.GPA);

// 输出过滤后的学生信息
Console.WriteLine("Filtered Students:");
foreach (var student in filteredStudents)
{
    Console.WriteLine($"Name: {student.Name}, Age: {student.Age}, GPA: {student.GPA}");
}

// 输出映射后的学生信息
Console.WriteLine("Mapped Students:");
foreach (var student in mappedStudents)
{
    Console.WriteLine($"Name: {student.Name}, GPA: {student.GPA}");
}

// 输出排序后的学生信息
Console.WriteLine("Sorted Students:");
foreach (var student in sortedStudents)
{
    Console.WriteLine($"Name: {student.Name}, Age: {student.Age}, GPA: {student.GPA}");
}

在上面的示例中,我们使用了 WhereSelectOrderByDescending 操作符分别进行过滤、映射和排序操作。这些操作符允许你以简洁的方式对集合进行处理,从而得到符合你需求的结果。记住,这些操作符返回的是一个新的查询对象,所以原始集合保持不变。

七、LINQ与数据库

7.1 使用LINQ进行数据库查询

使用LINQ进行数据库查询通常涉及使用ORM(对象关系映射)工具,如Entity Framework,它允许你将数据库中的表映射为.NET对象,并且使用LINQ进行查询操作。以下是在使用Entity Framework进行数据库查询时的基本示例:
假设我们有一个数据库表格 Students,包含学生的姓名、年龄和成绩信息。我们想要从数据库中选择年龄小于 22 的学生,并按成绩降序排列。

using System;
using System.Linq;

// 引入Entity Framework相关命名空间
using Microsoft.EntityFrameworkCore;

// 定义与数据库表对应的实体类
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public double GPA { get; set; }
}

// DbContext 表示数据库上下文
public class SchoolDbContext : DbContext
{
    public DbSet<Student> Students { get; set; }
    // 其他DbSet和配置可以在这里添加
}

class Program
{
    static void Main()
    {
        using (var dbContext = new SchoolDbContext())
        {
            // 过滤并排序:选择年龄小于 22 的学生,按成绩降序排列
            var query = dbContext.Students
                .Where(student => student.Age < 22)
                .OrderByDescending(student => student.GPA);

            // 执行查询
            foreach (var student in query)
            {
                Console.WriteLine($"Name: {student.Name}, Age: {student.Age}, GPA: {student.GPA}");
            }
        }
    }
}

在上面的示例中:

  1. 我们定义了一个 Student 类,它映射到数据库表 Students
  2. 创建了一个继承自 DbContextSchoolDbContext 类,其中包含了一个 DbSet 属性,用于表示学生数据表。
  3. Main 方法中,我们创建了一个 SchoolDbContext 实例,并使用LINQ操作 Students 集合。通过使用 Where 操作符过滤出年龄小于 22 的学生,然后使用 OrderByDescending 进行成绩降序排序。
  4. 最后,我们通过遍历 query 结果执行实际的查询,并输出结果。
7.1 使用Entity Framework和LINQ to SQL进行数据库操作

当使用 C# 编程语言时,可以使用 Entity Framework 和 LINQ to SQL 来进行数据库操作。这两个技术都是用于进行对象关系映射(ORM)的框架,它们使得将数据库操作转化为面向对象的代码更加容易。下面我将分别介绍一下 Entity Framework 和 LINQ to SQL 的基本用法。
Entity Framework:
Entity Framework 是一个功能强大的 ORM 框架,支持多种数据库引擎,能够帮助开发者将数据库中的数据映射到 .NET 对象中,并提供了 LINQ 查询语言的支持。以下是一个简单的示例,展示了如何使用 Entity Framework 进行数据库操作:

  1. 安装 Entity Framework: 在项目中使用 NuGet 包管理器安装 Entity Framework 包。
  2. 定义实体类: 定义 C# 类来映射数据库表格。
using System.ComponentModel.DataAnnotations;

public class Product
{
    [Key]
    public int ProductId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}
  1. 创建 DbContext 类: 创建继承自 DbContext 的类来定义数据库上下文。
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
}
  1. 进行数据库操作: 在你的代码中使用 DbContext 来进行数据库操作。
using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        using (var context = new ApplicationDbContext())
        {
            var products = context.Products.Where(p => p.Price > 50).ToList();

            foreach (var product in products)
            {
                Console.WriteLine($"Product: {product.Name}, Price: {product.Price}");
            }
        }
    }
}

LINQ to SQL:
LINQ to SQL 是另一种用于数据库操作的技术,它专注于与 SQL Server 数据库的交互。以下是一个简单的示例,展示了如何使用 LINQ to SQL 进行数据库操作:

  1. 创建 LINQ to SQL 类型: 在 Visual Studio 中创建一个 LINQ to SQL 类型(.dbml 文件),并将数据库表格拖放到设计表面。
  2. 进行数据库操作:
using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        using (var db = new DataClassesDataContext())
        {
            var products = from p in db.Products
                           where p.Price > 50
                           select p;

            foreach (var product in products)
            {
                Console.WriteLine($"Product: {product.ProductName}, Price: {product.Price}");
            }
        }
    }
}

八、LINQ与XML

8.1 使用LINQ查询和操作XML数据

使用 LINQ 查询和操作 XML 数据在 C# 中非常方便。LINQ to XML 提供了一种简洁的方式来查询、修改和创建 XML 文档。以下是一个示例代码,展示了如何使用 LINQ to XML 进行 XML 数据的查询和操作:

using System;
using System.Linq;
using System.Xml.Linq;

class Program
{
    static void Main(string[] args)
    {
        // 加载 XML 文档
        string xmlData = @"
            
                
                    Introduction to C#
                    John Smith
                    29.99
                
                
                    Advanced LINQ
                    Jane Doe
                    39.99
                
            ";

        XDocument doc = XDocument.Parse(xmlData);

        // 查询 XML 数据
        var query = from book in doc.Descendants("book")
                    where (double)book.Element("price") > 30.0
                    select new
                    {
                        Title = book.Element("title").Value,
                        Author = book.Element("author").Value,
                        Price = (double)book.Element("price")
                    };

        foreach (var book in query)
        {
            Console.WriteLine($"Title: {book.Title}, Author: {book.Author}, Price: {book.Price}");
        }

        // 修改 XML 数据
        XElement firstBook = doc.Descendants("book").First();
        firstBook.Element("price").Value = "34.99";

        // 添加新元素
        XElement newBook = new XElement("book",
            new XElement("title", "LINQ Unleashed"),
            new XElement("author", "Alice Johnson"),
            new XElement("price", "49.99"));
        doc.Root.Add(newBook);

        // 保存修改后的 XML 文档
        doc.Save("updated_books.xml");
    }
}

在这个示例中,我们首先加载一个 XML 字符串为 XDocument 对象。然后使用 LINQ 查询语法来筛选价格大于 30.0 的书籍。接着,我们修改了第一本书的价格,并添加了一本新书。最后,我们保存修改后的 XML 文档。

8.2 LINQ to XML的基本用法和语法

LINQ to XML 是 C# 中用于处理 XML 数据的一种技术,它提供了一种方便的方式来创建、查询和修改 XML 文档。以下是 LINQ to XML 的基本用法和语法示例:
1. 创建 XML 文档:

XDocument doc = new XDocument(
    new XElement("books",
        new XElement("book",
            new XElement("title", "Introduction to C#"),
            new XElement("author", "John Smith"),
            new XElement("price", "29.99")),
        new XElement("book",
            new XElement("title", "Advanced LINQ"),
            new XElement("author", "Jane Doe"),
            new XElement("price", "39.99"))
    )
);

2. 查询 XML 数据:

var query = from book in doc.Descendants("book")
            where (double)book.Element("price") > 30.0
            select new
            {
                Title = book.Element("title").Value,
                Author = book.Element("author").Value,
                Price = (double)book.Element("price")
            };

foreach (var book in query)
{
    Console.WriteLine($"Title: {book.Title}, Author: {book.Author}, Price: {book.Price}");
}

3. 修改 XML 数据:

XElement firstBook = doc.Descendants("book").First();
firstBook.Element("price").Value = "34.99";

// 添加新元素
XElement newBook = new XElement("book",
    new XElement("title", "LINQ Unleashed"),
    new XElement("author", "Alice Johnson"),
    new XElement("price", "49.99"));
doc.Root.Add(newBook);

4. 保存修改后的 XML 文档:

doc.Save("updated_books.xml");

在 LINQ to XML 中,你可以使用类似 LINQ 查询的语法来查询和修改 XML 数据。以下是一些常用的 LINQ to XML 方法和属性:

  • XDocument:表示整个 XML 文档。
  • XElement:表示 XML 元素。
  • XAttribute:表示 XML 属性。
  • Descendants:获取指定名称的所有子元素。
  • Elements:获取指定名称的直接子元素。
  • Value:获取元素的值。
  • Add:添加新元素或属性。
  • Remove:移除元素或属性。
  • Save:保存 XML 文档。

九、自定义LINQ查询

9.1 创建和使用扩展方法

在 C# 中,LINQ 扩展方法是一种允许你自定义 LINQ 查询操作的方式。你可以创建自己的 LINQ 扩展方法来在 LINQ 查询中添加自定义的功能或操作。以下是创建和使用 LINQ 扩展方法的基本步骤:
创建 LINQ 扩展方法:

  1. 创建一个静态类: 创建一个静态类,用于包含你的 LINQ 扩展方法。
using System;
using System.Collections.Generic;

public static class MyLinqExtensions
{
    // 定义扩展方法
    public static IEnumerable<T> WhereGreaterThan<T>(this IEnumerable<T> source, T threshold)
        where T : IComparable<T>
    {
        foreach (var item in source)
        {
            if (item.CompareTo(threshold) > 0)
            {
                yield return item;
            }
        }
    }
}

在上述代码中,我们创建了一个名为 MyLinqExtensions 的静态类,并在其中定义了一个扩展方法 WhereGreaterThan
2. 使用 this 关键字: 在扩展方法的第一个参数前加上 this 关键字,表示该方法是一个扩展方法,并且作用于该类型的实例。
使用 LINQ 扩展方法:

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        List<int> numbers = new List<int> { 1, 5, 9, 12, 15, 18, 20 };

        // 使用自定义的扩展方法
        var filteredNumbers = numbers.WhereGreaterThan(10);

        foreach (var num in filteredNumbers)
        {
            Console.WriteLine(num);
        }
    }
}

在上述示例中,我们在 List 类型上使用了自定义的 WhereGreaterThan 扩展方法。该方法会筛选出大于指定阈值的元素。

Tip:扩展方法需要定义在静态类中,且命名空间要正确导入,才能被正常使用。

9.2 自定义LINQ查询操作符

在 C# 中,你可以创建自定义的 LINQ 查询操作符,以扩展 LINQ 查询语法,使其支持你自定义的查询操作。以下是一个示例,展示了如何创建和使用自定义的 LINQ 查询操作符:
创建自定义的 LINQ 查询操作符:

using System;
using System.Collections.Generic;

public static class MyLinqOperators
{
    public static IEnumerable<T> CustomFilter<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
        foreach (var item in source)
        {
            if (predicate(item))
            {
                yield return item;
            }
        }
    }
}

在上述代码中,我们创建了一个名为 MyLinqOperators 的静态类,并在其中定义了一个自定义的查询操作符 CustomFilter。该操作符会筛选出满足指定条件的元素。
使用自定义的 LINQ 查询操作符:

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        List<int> numbers = new List<int> { 1, 5, 9, 12, 15, 18, 20 };

        // 使用自定义的查询操作符
        var filteredNumbers = numbers.CustomFilter(num => num > 10);

        foreach (var num in filteredNumbers)
        {
            Console.WriteLine(num);
        }
    }
}

在上述示例中,我们使用了自定义的 CustomFilter 查询操作符来筛选出大于 10 的元素。

十、LINQ查询性能和优化

优化 LINQ 查询的性能是一个重要的课题,特别是在处理大量数据时。虽然 LINQ 提供了方便的查询语法,但不当的使用方式可能导致性能下降。以下是一些优化 LINQ 查询性能的建议:

  1. 选择适当的数据源: 选择最适合你查询需求的数据源,如 ListIEnumerableIQueryable 等。IQueryable 允许将查询延迟到数据库服务器,以提高效率。
  2. 使用合适的查询操作符: 选择适合问题的查询操作符,避免使用不必要的操作符,以减少不必要的开销。
  3. 延迟加载: 尽量使用延迟加载,只加载需要的数据。如果不需要所有结果,可以使用 Take()Skip() 方法来限制返回的数据量。
  4. 索引: 如果你的数据源支持索引,确保在查询中使用了索引字段,以加速数据检索。
  5. 使用索引字段进行过滤: 如果可能,使用索引字段进行筛选,以便数据库可以更快地定位所需的数据。
  6. 避免 N+1 查询问题: 当涉及到关联数据时,使用 Include() 或者投影(Select())来避免 N+1 查询问题,减少数据库交互次数。
  7. 合并多个操作: 尽量合并多个操作为一个查询,以减少迭代次数。
  8. 避免在循环中执行查询: 将查询移到循环外部,避免在每次迭代中都执行一次查询。
  9. 使用索引或哈希表进行查找: 如果需要频繁查找数据,可以考虑使用索引或者哈希表数据结构,以获得更高的查询性能。
  10. 使用合适的数据缓存: 对于不频繁变化的数据,可以考虑使用缓存来提高查询性能。
  11. 避免不必要的数据转换: 尽量避免在查询中频繁进行数据类型转换,以减少开销。
  12. 使用异步操作: 在适当的场景下,使用异步查询可以提高并发性能。
  13. 性能测试和分析: 使用性能测试工具和分析器,评估查询的性能瓶颈,并找到优化的机会。

十一、总结

LINQ 是一项强大的技术,为 C# 开发者提供了一种方便、灵活的查询和操作数据的方式,大大提高了代码的可读性和生产效率。无论是对内存中的数据还是对数据库、XML 等数据源,都可以使用统一的语法来进行操作。

你可能感兴趣的:(深入浅出C#,c#,linq,solr)