C# Linq 全网宇宙最详细保姆级教程(全是干货,建议收藏)

~


LINQ概述

LINQ(Language Integrated Query)是.NET Framework的一个重要特性,它允许开发人员使用类似SQL的语法在多种数据源上进行查询操作。LINQ支持的数据源包括但不限于:

  • 内存中的集合(如列表和数组)
  • 数据库(如SQL Server)
  • XML文档
  • Web服务

LINQ的基本概念

查询表达式

查询表达式是一种声明式的编程模型,用于指定需要执行的数据操作。其基本结构如下:

var query = from source in collection
            where condition
            orderby key
            select result;
  • from source in collection:指定查询的数据源。
  • where condition:指定筛选条件。
  • orderby key:指定排序条件。
  • select result:定义查询结果。
标准查询运算符

标准查询运算符是一组扩展方法,它们提供了一种函数式的方式来构建查询。这些方法定义在System.Linq.Enumerable类中,适用于所有实现了IEnumerable<T>接口的集合。常见的标准查询运算符包括:

  • Where:过滤集合中的元素。
  • Select:投影每个元素。
  • OrderBy 和 OrderByDescending:按升序或降序排序。
  • GroupBy:根据键值对元素进行分组。
  • Join:将两个集合基于键值进行关联。
  • Any 和 All:检查集合是否满足某些条件。
  • Count 和 Sum:计算集合的大小或求和。

LINQ to Objects

示例1:基本查询

假设我们有一个整数列表,想要找出其中的所有偶数并按降序排列:

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

var evenNumbers = from n in numbers
                  where n % 2 == 0
                  orderby n descending
                  select n;

foreach (var num in evenNumbers)
{
    Console.WriteLine(num);
}
示例2:投影和分组

假设我们有一个包含学生信息的列表,我们想按班级分组并计算每个班级的学生人数:

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

List<Student> students = new List<Student>
{
    new Student { Name = "Alice", Age = 20, Class = "A" },
    new Student { Name = "Bob", Age = 22, Class = "B" },
    new Student { Name = "Charlie", Age = 21, Class = "A" },
    new Student { Name = "David", Age = 23, Class = "B" },
    new Student { Name = "Eve", Age = 20, Class = "C" }
};

var studentGroups = from s in students
                    group s by s.Class into g
                    select new { Class = g.Key, Count = g.Count() };

foreach (var group in studentGroups)
{
    Console.WriteLine($"Class: {group.Class}, Number of Students: {group.Count}");
}

LINQ to SQL

设置数据库连接

要使用LINQ to SQL,首先需要添加对System.Data.Linq命名空间的引用,并设置与数据库的连接。

using System.Data.Linq;
using System.Data.Linq.Mapping;

string connectionString = "Data Source=myServerAddress;Initial Catalog=myDatabase;User Id=myUsername;Password=myPassword;";
var db = new DataContext(connectionString);
定义数据模型

使用LINQ to SQL时,需要定义数据模型类。这些类通常标记为[Table]属性,并且每个属性对应数据库表中的一个列。

[Table(Name = "Products")]
public class Product
{
    [Column(IsPrimaryKey = true)]
    public int ProductID { get; set; }

    [Column]
    public string ProductName { get; set; }

    [Column]
    public decimal UnitPrice { get; set; }
}
查询数据库

使用LINQ to SQL查询数据库非常直观。以下是一个示例,查询价格大于10美元的产品:

var products = from p in db.GetTable<Product>()
               where p.UnitPrice > 10
               select p;

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

LINQ to XML

创建XML文档

使用LINQ to XML可以轻松地创建和操作XML文档。以下是一个示例,创建一个简单的XML文档:

using System.Xml.Linq;

XElement books = new XElement("Books",
    new XElement("Book",
        new XAttribute("ISBN", "123456"),
        new XElement("Title", "C# Programming"),
        new XElement("Author", "John Doe"),
        new XElement("Price", 39.99m)
    ),
    new XElement("Book",
        new XAttribute("ISBN", "789012"),
        new XElement("Title", "LINQ in Action"),
        new XElement("Author", "Jane Smith"),
        new XElement("Price", 49.99m)
    )
);

Console.WriteLine(books);
查询XML文档

使用LINQ to XML查询XML文档也非常方便。以下是一个示例,查询所有价格大于40美元的书籍:

var expensiveBooks = from book in books.Elements("Book")
                     where (decimal)book.Element("Price") > 40
                     select book;

foreach (var book in expensiveBooks)
{
    Console.WriteLine(book.Element("Title").Value);
}

LINQ提供了一种强大且灵活的方式来查询和操作各种数据源。通过使用LINQ,开发人员可以编写更加简洁、可读性强的代码。


高级LINQ用法

多表连接

在实际应用中,经常需要从多个表中获取数据。LINQ提供了多种连接操作符,如JoinGroupJoinLeft Join等。

示例:内连接(Inner Join)

假设我们有两个表:CustomersOrders,我们想找出每个客户的订单信息。

class Customer
{
    public int CustomerID { get; set; }
    public string Name { get; set; }
}

class Order
{
    public int OrderID { get; set; }
    public int CustomerID { get; set; }
    public DateTime OrderDate { get; set; }
}

List<Customer> customers = new List<Customer>
{
    new Customer { CustomerID = 1, Name = "Alice" },
    new Customer { CustomerID = 2, Name = "Bob" },
    new Customer { CustomerID = 3, Name = "Charlie" }
};

List<Order> orders = new List<Order>
{
    new Order { OrderID = 101, CustomerID = 1, OrderDate = DateTime.Parse("2023-01-01") },
    new Order { OrderID = 102, CustomerID = 1, OrderDate = DateTime.Parse("2023-02-01") },
    new Order { OrderID = 103, CustomerID = 2, OrderDate = DateTime.Parse("2023-03-01") }
};

var customerOrders = from c in customers
                     join o in orders on c.CustomerID equals o.CustomerID
                     select new { CustomerName = c.Name, OrderID = o.OrderID, OrderDate = o.OrderDate };

foreach (var item in customerOrders)
{
    Console.WriteLine($"{item.CustomerName} ordered {item.OrderID} on {item.OrderDate}");
}
示例:左连接(Left Join)

如果想保留所有客户,即使他们没有订单记录,可以使用左连接。

var customerOrders = from c in customers
                     join o in orders on c.CustomerID equals o.CustomerID into co
                     from o in co.DefaultIfEmpty()
                     select new { CustomerName = c.Name, OrderID = o?.OrderID, OrderDate = o?.OrderDate };

foreach (var item in customerOrders)
{
    Console.WriteLine($"{item.CustomerName} ordered {item.OrderID} on {item.OrderDate}");
}
聚合操作

LINQ提供了多种聚合操作符,如CountSumMinMaxAverage等,用于对集合进行统计计算。

示例:计算总和和平均值

假设我们有一个包含产品价格的列表,我们想计算总价格和平均价格。

List<decimal> prices = new List<decimal> { 10.5m, 20.3m, 30.7m, 40.2m };

decimal total = prices.Sum();
decimal average = prices.Average();

Console.WriteLine($"Total Price: {total}");
Console.WriteLine($"Average Price: {average}");
延迟执行

LINQ查询采用延迟执行机制,即查询不会在定义时立即执行,而是在遍历结果时才执行。这种机制可以提高性能,因为它只在需要时才执行必要的操作。

示例:延迟执行
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var evenNumbers = from n in numbers
                  where n % 2 == 0
                  select n;

// 查询尚未执行
Console.WriteLine("Query defined");

// 遍历结果时查询才被执行
foreach (var num in evenNumbers)
{
    Console.WriteLine(num);
}
强制立即执行

有时候我们需要强制立即执行查询,可以使用ToListToArrayToDictionary等方法。

示例:立即执行
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var evenNumbers = (from n in numbers
                   where n % 2 == 0
                   select n).ToList();

// 查询已经立即执行
Console.WriteLine("Query executed");

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

性能优化

虽然LINQ提供了强大的查询功能,但在处理大量数据时,性能优化是非常重要的。以下是一些常见的优化技巧:

  1. 减少数据传输量:尽量在数据库中进行过滤和聚合操作,减少传输到应用程序的数据量。
  2. 避免多次查询:尽量在一个查询中完成所有操作,避免多次查询数据库。
  3. 使用索引:确保数据库表中有适当的索引,以提高查询性能。
  4. 缓存结果:对于不经常变化的数据,可以考虑缓存查询结果,减少重复查询。
示例:优化查询

假设我们有一个包含大量产品的数据库表,我们想找出价格最高的前10个产品。

var top10ExpensiveProducts = from p in db.GetTable<Product>()
                            where p.UnitPrice > 10
                            orderby p.UnitPrice descending
                            select p;

// 只取前10个结果
var top10 = top10ExpensiveProducts.Take(10).ToList();

foreach (var product in top10)
{
    Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
}

LINQ的强大之处在于它提供了一种统一的方式处理不同数据源,使得代码更加简洁和易读。


异步查询

在处理大型数据集或网络请求时,异步查询可以显著提高应用程序的响应性和性能。LINQ to Entities 和 LINQ to SQL 支持异步查询。

示例:异步查询

假设我们有一个包含大量产品的数据库表,我们想异步查询价格最高的前10个产品。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Data.Linq;

class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
}

class Program
{
    static async Task Main(string[] args)
    {
        string connectionString = "Data Source=myServerAddress;Initial Catalog=myDatabase;User Id=myUsername;Password=myPassword;";
        var db = new DataContext(connectionString);

        var top10ExpensiveProducts = from p in db.GetTable<Product>()
                                     where p.UnitPrice > 10
                                     orderby p.UnitPrice descending
                                     select p;

        // 异步执行查询并取前10个结果
        var top10 = await top10ExpensiveProducts.Take(10).ToListAsync();

        foreach (var product in top10)
        {
            Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
        }
    }
}

异常处理

在LINQ查询中,异常处理是非常重要的,尤其是在处理外部数据源时。可以使用try-catch块来捕获和处理异常。

示例:异常处理
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        try
        {
            var evenNumbers = from n in numbers
                              where n % 0 == 0 // 这里故意制造一个除零错误
                              select n;

            foreach (var num in evenNumbers)
            {
                Console.WriteLine(num);
            }
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Error: Division by zero occurred.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
    }
}

扩展方法

LINQ的核心是一系列扩展方法,这些方法定义在System.Linq.Enumerable类中。你可以自定义扩展方法来扩展LINQ的功能。

示例:自定义扩展方法
using System;
using System.Collections.Generic;
using System.Linq;

static class LinqExtensions
{
    public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
    {
        Queue<T> queue = new Queue<T>();
        foreach (T item in source)
        {
            if (queue.Count >= count)
            {
                yield return queue.Dequeue();
            }
            queue.Enqueue(item);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        var result = numbers.SkipLast(3);

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

LINQ与Lambda表达式

除了查询表达式,LINQ还支持使用Lambda表达式来编写查询。Lambda表达式提供了一种更紧凑和灵活的方式来定义查询逻辑。

示例:使用Lambda表达式
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        // 使用Lambda表达式进行查询
        var evenNumbers = numbers.Where(n => n % 2 == 0);

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

组合查询

在复杂的场景中,可能需要组合多个查询操作。LINQ提供了多种方式来组合查询,包括链式调用和嵌套查询。

示例:组合查询
using System;
using System.Collections.Generic;
using System.Linq;

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

class Program
{
    static void Main(string[] args)
    {
        List<Student> students = new List<Student>
        {
            new Student { Name = "Alice", Age = 20, Class = "A" },
            new Student { Name = "Bob", Age = 22, Class = "B" },
            new Student { Name = "Charlie", Age = 21, Class = "A" },
            new Student { Name = "David", Age = 23, Class = "B" },
            new Student { Name = "Eve", Age = 20, Class = "C" }
        };

        // 组合查询:按年龄筛选,按班级分组,计算每个班级的学生人数
        var studentGroups = students
            .Where(s => s.Age > 20)
            .GroupBy(s => s.Class)
            .Select(g => new { Class = g.Key, Count = g.Count() });

        foreach (var group in studentGroups)
        {
            Console.WriteLine($"Class: {group.Class}, Number of Students: {group.Count}");
        }
    }
}

复杂的查询模式

子查询

子查询是指在一个查询中嵌套另一个查询。子查询可以用于更复杂的筛选和投影操作。

示例:子查询

假设我们有一个包含学生和课程的数据库,我们想找出每个学生所选的课程名称。

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

class Student
{
    public int StudentID { get; set; }
    public string Name { get; set; }
}

class Course
{
    public int CourseID { get; set; }
    public string CourseName { get; set; }
}

class Enrollment
{
    public int EnrollmentID { get; set; }
    public int StudentID { get; set; }
    public int CourseID { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<Student> students = new List<Student>
        {
            new Student { StudentID = 1, Name = "Alice" },
            new Student { StudentID = 2, Name = "Bob" },
            new Student { StudentID = 3, Name = "Charlie" }
        };

        List<Course> courses = new List<Course>
        {
            new Course { CourseID = 101, CourseName = "Math" },
            new Course { CourseID = 102, CourseName = "Science" },
            new Course { CourseID = 103, CourseName = "History" }
        };

        List<Enrollment> enrollments = new List<Enrollment>
        {
            new Enrollment { EnrollmentID = 1, StudentID = 1, CourseID = 101 },
            new Enrollment { EnrollmentID = 2, StudentID = 1, CourseID = 102 },
            new Enrollment { EnrollmentID = 3, StudentID = 2, CourseID = 103 },
            new Enrollment { EnrollmentID = 4, StudentID = 3, CourseID = 101 }
        };

        var studentCourses = from s in students
                            select new
                            {
                                StudentName = s.Name,
                                Courses = from e in enrollments
                                          where e.StudentID == s.StudentID
                                          join c in courses on e.CourseID equals c.CourseID
                                          select c.CourseName
                            };

        foreach (var student in studentCourses)
        {
            Console.WriteLine($"Student: {student.StudentName}");
            foreach (var course in student.Courses)
            {
                Console.WriteLine($"  Course: {course}");
            }
        }
    }
}
分组和聚合

分组和聚合是LINQ中常用的高级操作。通过分组可以将数据分成多个子集,然后对每个子集进行聚合操作。

示例:分组和聚合

假设我们有一个包含销售记录的列表,我们想按产品类别分组并计算每个类别的总销售额。

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

class Sale
{
    public int SaleID { get; set; }
    public string ProductCategory { get; set; }
    public decimal Amount { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<Sale> sales = new List<Sale>
        {
            new Sale { SaleID = 1, ProductCategory = "Electronics", Amount = 200.0m },
            new Sale { SaleID = 2, ProductCategory = "Electronics", Amount = 150.0m },
            new Sale { SaleID = 3, ProductCategory = "Clothing", Amount = 100.0m },
            new Sale { SaleID = 4, ProductCategory = "Clothing", Amount = 120.0m },
            new Sale { SaleID = 5, ProductCategory = "Books", Amount = 50.0m }
        };

        var salesSummary = from s in sales
                           group s by s.ProductCategory into g
                           select new
                           {
                               Category = g.Key,
                               TotalSales = g.Sum(s => s.Amount)
                           };

        foreach (var summary in salesSummary)
        {
            Console.WriteLine($"Category: {summary.Category}, Total Sales: {summary.TotalSales}");
        }
    }
}

性能优化技巧

1. 使用 AsEnumerable 和 AsQueryable

在处理大数据集时,AsEnumerableAsQueryable 可以帮助你控制查询的执行位置。AsEnumerable 将查询转换为客户端执行,而 AsQueryable 则保持服务器端执行。

示例:使用 AsEnumerable
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        // 在客户端执行筛选和投影
        var evenNumbers = numbers.AsEnumerable().Where(n => n % 2 == 0);

        foreach (var num in evenNumbers)
        {
            Console.WriteLine(num);
        }
    }
}
2. 避免不必要的投影

在投影操作中,尽量减少返回的字段数量,只选择需要的字段。

示例:避免不必要的投影
using System;
using System.Collections.Generic;
using System.Linq;

class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
    public string Description { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<Product> products = new List<Product>
        {
            new Product { ProductID = 1, ProductName = "Product A", UnitPrice = 10.0m, Description = "Description A" },
            new Product { ProductID = 2, ProductName = "Product B", UnitPrice = 20.0m, Description = "Description B" },
            new Product { ProductID = 3, ProductName = "Product C", UnitPrice = 30.0m, Description = "Description C" }
        };

        // 只选择需要的字段
        var productNames = from p in products
                           select p.ProductName;

        foreach (var name in productNames)
        {
            Console.WriteLine(name);
        }
    }
}
3. 使用索引

在数据库查询中,确保相关的字段上有适当的索引,以提高查询性能。

LINQ与LINQ to Entities

LINQ to Entities 是 Entity Framework 中的一部分,用于查询和操作关系型数据库。它提供了与 LINQ to SQL 类似的功能,但更加灵活和强大。

示例:LINQ to Entities

假设我们有一个使用 Entity Framework 的数据库上下文 MyDbContext,其中包含 Products 表。

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;

class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
}

class MyDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Data Source=myServerAddress;Initial Catalog=myDatabase;User Id=myUsername;Password=myPassword;");
    }
}

class Program
{
    static void Main(string[] args)
    {
        using (var context = new MyDbContext())
        {
            var expensiveProducts = from p in context.Products
                                    where p.UnitPrice > 10
                                    orderby p.UnitPrice descending
                                    select p;

            foreach (var product in expensiveProducts)
            {
                Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
            }
        }
    }
}

最佳实践

  1. 避免在查询中使用复杂的方法调用:尽量在查询外部处理复杂的逻辑,以避免影响查询性能。

  2. 使用分页:在处理大量数据时,使用分页可以显著提高性能。

  3. 缓存结果:对于不经常变化的数据,可以考虑缓存查询结果,减少重复查询。

  4. 使用延迟加载:在需要时才加载相关数据,可以减少初始加载的时间和资源消耗。

  5. 编写可读性强的代码:尽量使用查询表达式来编写可读性强的代码,必要时再使用 Lambda 表达式。


分页查询

分页查询在处理大量数据时非常重要,它可以显著提高应用程序的性能和响应速度。LINQ 提供了 SkipTake 方法来实现分页。

示例:分页查询

假设我们有一个包含大量产品的数据库表,我们想分页显示每页10个产品。

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

class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<Product> products = new List<Product>
        {
            new Product { ProductID = 1, ProductName = "Product A", UnitPrice = 10.0m },
            new Product { ProductID = 2, ProductName = "Product B", UnitPrice = 20.0m },
            new Product { ProductID = 3, ProductName = "Product C", UnitPrice = 30.0m },
            // ... 添加更多产品
        };

        int pageSize = 10;
        int pageNumber = 1; // 当前页码

        var pagedProducts = products
            .OrderBy(p => p.ProductID)
            .Skip((pageNumber - 1) * pageSize)
            .Take(pageSize);

        foreach (var product in pagedProducts)
        {
            Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
        }
    }
}

并行查询

并行查询可以利用多核处理器的优势,提高查询性能。LINQ 提供了 Parallel 类来实现并行查询。

示例:并行查询

假设我们有一个包含大量数字的列表,我们想并行计算所有偶数的平方。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        List<int> numbers = new List<int>();
        for (int i = 1; i <= 1000000; i++)
        {
            numbers.Add(i);
        }

        var evenSquares = ParallelEnumerable.Range(1, 1000000)
            .Where(n => n % 2 == 0)
            .Select(n => n * n)
            .ToList();

        Console.WriteLine($"Number of even squares: {evenSquares.Count}");
    }
}

LINQ to JSON

LINQ to JSON 允许你轻松地创建、解析和操作 JSON 数据。使用 Json.NET 库(也称为 Newtonsoft.Json)可以实现这一功能。

示例:LINQ to JSON

假设我们有一个 JSON 字符串,我们想解析它并提取特定的信息。

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        {
            ""name"": ""John Doe"",
            ""age"": 30,
            ""address"": {
                ""street"": ""123 Main St"",
                ""city"": ""Anytown"",
                ""state"": ""CA""
            },
            ""phoneNumbers"": [
                ""123-456-7890"",
                ""987-654-3210""
            ]
        }";

        JObject obj = JObject.Parse(json);

        string name = (string)obj["name"];
        int age = (int)obj["age"];
        string city = (string)obj["address"]["city"];
        List<string> phoneNumbers = obj["phoneNumbers"].Select(t => (string)t).ToList();

        Console.WriteLine($"Name: {name}");
        Console.WriteLine($"Age: {age}");
        Console.WriteLine($"City: {city}");
        Console.WriteLine($"Phone Numbers: {string.Join(", ", phoneNumbers)}");
    }
}

LINQ to CSV

LINQ to CSV 允许你轻松地读取和写入 CSV 文件。使用 FileHelpers 库可以实现这一功能。

示例:LINQ to CSV

假设我们有一个 CSV 文件,我们想读取它并提取特定的信息。

  1. 安装 FileHelpers 库:

    dotnet add package FileHelpers
  2. 使用 FileHelpers 读取 CSV 文件:

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

[DelimitedRecord(",")]
class Product
{
    public int ProductID;
    public string ProductName;
    public decimal UnitPrice;
}

class Program
{
    static void Main(string[] args)
    {
        string filePath = "products.csv";

        var engine = new FileHelperEngine<Product>();
        var products = engine.ReadFile(filePath).ToList();

        var expensiveProducts = products
            .Where(p => p.UnitPrice > 10)
            .OrderByDescending(p => p.UnitPrice);

        foreach (var product in expensiveProducts)
        {
            Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
        }
    }
}

高级调试和测试技巧

调试 LINQ 查询

调试 LINQ 查询时,可以使用 ToListToArray 方法将查询结果转换为集合,以便在调试器中查看。

示例:调试 LINQ 查询
using System;
using System.Collections.Generic;
using System.Linq;

class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<Product> products = new List<Product>
        {
            new Product { ProductID = 1, ProductName = "Product A", UnitPrice = 10.0m },
            new Product { ProductID = 2, ProductName = "Product B", UnitPrice = 20.0m },
            new Product { ProductID = 3, ProductName = "Product C", UnitPrice = 30.0m }
        };

        var expensiveProducts = products
            .Where(p => p.UnitPrice > 10)
            .OrderByDescending(p => p.UnitPrice)
            .ToList(); // 转换为列表以便调试

        foreach (var product in expensiveProducts)
        {
            Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
        }
    }
}
单元测试 LINQ 查询

使用单元测试框架(如 NUnit 或 xUnit)可以对 LINQ 查询进行测试,确保查询的正确性。

示例:单元测试 LINQ 查询
  1. 安装 NUnit 测试框架:

    dotnet add package NUnit
    dotnet add package NUnit3TestAdapter
  2. 编写单元测试:

using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
}

[TestFixture]
public class ProductTests
{
    private List<Product> _products;

    [SetUp]
    public void Setup()
    {
        _products = new List<Product>
        {
            new Product { ProductID = 1, ProductName = "Product A", UnitPrice = 10.0m },
            new Product { ProductID = 2, ProductName = "Product B", UnitPrice = 20.0m },
            new Product { ProductID = 3, ProductName = "Product C", UnitPrice = 30.0m }
        };
    }

    [Test]
    public void Test_Expensive_Products()
    {
        var expensiveProducts = _products
            .Where(p => p.UnitPrice > 10)
            .OrderByDescending(p => p.UnitPrice)
            .ToList();

        Assert.AreEqual(2, expensiveProducts.Count);
        Assert.AreEqual("Product C", expensiveProducts[0].ProductName);
        Assert.AreEqual("Product B", expensiveProducts[1].ProductName);
    }
}

动态查询

动态查询允许你在运行时构建查询条件,这对于构建灵活的用户界面或API非常有用。可以使用 System.Linq.Dynamic.Core 库来实现动态查询。

示例:动态查询
  1. 安装 System.Linq.Dynamic.Core 库:

    dotnet add package System.Linq.Dynamic.Core
  2. 使用动态查询:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;

class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<Product> products = new List<Product>
        {
            new Product { ProductID = 1, ProductName = "Product A", UnitPrice = 10.0m },
            new Product { ProductID = 2, ProductName = "Product B", UnitPrice = 20.0m },
            new Product { ProductID = 3, ProductName = "Product C", UnitPrice = 30.0m }
        };

        // 动态构建查询条件
        string filter = "UnitPrice > 10";
        string orderBy = "UnitPrice desc";

        var dynamicQuery = products.AsQueryable().Where(filter).OrderBy(orderBy);

        foreach (var product in dynamicQuery)
        {
            Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
        }
    }
}

LINQ与EF Core的结合使用

Entity Framework Core (EF Core) 是一个轻量级、可扩展且支持多种数据库的ORM。结合LINQ使用EF Core可以让你更高效地操作数据库。

示例:LINQ与EF Core

假设我们有一个使用EF Core的数据库上下文 MyDbContext,其中包含 Products 表。

  1. 安装 EF Core:

    dotnet add package Microsoft.EntityFrameworkCore.SqlServer
  2. 定义数据库上下文和实体:

using Microsoft.EntityFrameworkCore;

class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
}

class MyDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Data Source=myServerAddress;Initial Catalog=myDatabase;User Id=myUsername;Password=myPassword;");
    }
}
  1. 使用 LINQ 查询数据库:
using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        using (var context = new MyDbContext())
        {
            var expensiveProducts = from p in context.Products
                                    where p.UnitPrice > 10
                                    orderby p.UnitPrice descending
                                    select p;

            foreach (var product in expensiveProducts)
            {
                Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
            }
        }
    }
}

LINQ表达式树

LINQ表达式树允许你将LINQ查询转换为表达式树,这在编译时生成代码,可以在运行时动态执行。表达式树在构建动态查询和自定义查询处理器时非常有用。

示例:LINQ表达式树
using System;
using System.Linq;
using System.Linq.Expressions;

class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<Product> products = new List<Product>
        {
            new Product { ProductID = 1, ProductName = "Product A", UnitPrice = 10.0m },
            new Product { ProductID = 2, ProductName = "Product B", UnitPrice = 20.0m },
            new Product { ProductID = 3, ProductName = "Product C", UnitPrice = 30.0m }
        };

        // 构建表达式树
        ParameterExpression param = Expression.Parameter(typeof(Product), "p");
        Expression<Func<Product, bool>> filter = Expression.Lambda<Func<Product, bool>>(
            Expression.GreaterThan(
                Expression.Property(param, "UnitPrice"),
                Expression.Constant(10.0m)
            ),
            param
        );

        // 使用表达式树进行查询
        var expensiveProducts = products.AsQueryable().Where(filter);

        foreach (var product in expensiveProducts)
        {
            Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
        }
    }
}

高级性能优化技巧

1. 使用 AsNoTracking

在不需要跟踪实体更改的情况下,使用 AsNoTracking 可以显著提高查询性能。

using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        using (var context = new MyDbContext())
        {
            var products = context.Products.AsNoTracking().ToList();

            foreach (var product in products)
            {
                Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
            }
        }
    }
}
2. 使用 CompiledQuery

编译查询可以减少每次查询的解析开销,提高性能。

using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        using (var context = new MyDbContext())
        {
            Func<MyDbContext, decimal, IQueryable<Product>> compiledQuery =
                CompiledQuery.Compile((MyDbContext db, decimal minPrice) =>
                    from p in db.Products
                    where p.UnitPrice > minPrice
                    select p);

            var expensiveProducts = compiledQuery(context, 10.0m);

            foreach (var product in expensiveProducts)
            {
                Console.WriteLine($"{product.ProductName}: {product.UnitPrice}");
            }
        }
    }
}
3. 使用索引

确保数据库表上有适当的索引,特别是用于过滤和排序的字段。

4. 减少不必要的投影

只选择需要的字段,避免不必要的投影。

using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        using (var context = new MyDbContext())
        {
            var productNames = from p in context.Products
                               where p.UnitPrice > 10
                               select p.ProductName;

            foreach (var name in productNames)
            {
                Console.WriteLine(name);
            }
        }
    }
}


查询优化技巧

1. 使用 Include 进行预加载

在处理关联数据时,使用 Include 方法可以预加载相关实体,避免多次查询数据库。

示例:使用 Include

假设我们有一个 Product 实体和一个 Category 实体,Product 有一个外键引用 Category

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;

class Category
{
    public int CategoryID { get; set; }
    public string CategoryName { get; set; }
    public ICollection<Product> Products { get; set; }
}

class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
    public int CategoryID { get; set; }
    public Category Category { get; set; }
}

class MyDbContext : DbContext
{
    public DbSet<Category> Categories { get; set; }
    public DbSet<Product> Products { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Data Source=myServerAddress;Initial Catalog=myDatabase;User Id=myUsername;Password=myPassword;");
    }
}

class Program
{
    static void Main(string[] args)
    {
        using (var context = new MyDbContext())
        {
            var productsWithCategories = context.Products
                .Include(p => p.Category)
                .Where(p => p.UnitPrice > 10)
                .OrderByDescending(p => p.UnitPrice)
                .ToList();

            foreach (var product in productsWithCategories)
            {
                Console.WriteLine($"{product.ProductName} ({product.Category.CategoryName}): {product.UnitPrice}");
            }
        }
    }
}
2. 使用 AsSplitQuery 优化多表查询

在EF Core 5.0及以上版本中,可以使用 AsSplitQuery 方法来优化多表查询,避免笛卡尔积问题。

示例:使用 AsSplitQuery
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;

class Program
{
    static void Main(string[] args)
    {
        using (var context = new MyDbContext())
        {
            var productsWithCategories = context.Products
                .Include(p => p.Category)
                .Where(p => p.UnitPrice > 10)
                .OrderByDescending(p => p.UnitPrice)
                .AsSplitQuery()
                .ToList();

            foreach (var product in productsWithCategories)
            {
                Console.WriteLine($"{product.ProductName} ({product.Category.CategoryName}): {product.UnitPrice}");
            }
        }
    }
}

LINQ与内存数据结构的结合使用

LINQ不仅可以用于数据库查询,还可以用于内存中的数据结构,如列表、数组等。

示例:LINQ与内存数据结构

假设我们有一个包含员工信息的列表,我们想找出每个部门的最高薪资。

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

class Employee
{
    public int EmployeeID { get; set; }
    public string Name { get; set; }
    public string Department { get; set; }
    public decimal Salary { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        List<Employee> employees = new List<Employee>
        {
            new Employee { EmployeeID = 1, Name = "Alice", Department = "HR", Salary = 50000.0m },
            new Employee { EmployeeID = 2, Name = "Bob", Department = "IT", Salary = 70000.0m },
            new Employee { EmployeeID = 3, Name = "Charlie", Department = "HR", Salary = 60000.0m },
            new Employee { EmployeeID = 4, Name = "David", Department = "IT", Salary = 80000.0m },
            new Employee { EmployeeID = 5, Name = "Eve", Department = "Finance", Salary = 90000.0m }
        };

        var highestSalaries = employees
            .GroupBy(e => e.Department)
            .Select(g => new
            {
                Department = g.Key,
                HighestSalary = g.Max(e => e.Salary),
                EmployeeName = g.First(e => e.Salary == g.Max(s => s.Salary)).Name
            });

        foreach (var department in highestSalaries)
        {
            Console.WriteLine($"Department: {department.Department}, Highest Salary: {department.HighestSalary}, Employee: {department.EmployeeName}");
        }
    }
}

LINQ与XML的结合使用

LINQ to XML 提供了一种强大的方式来创建、查询和修改XML文档。

示例:LINQ to XML

假设我们有一个XML文件,我们想从中提取特定的信息。

  1. 创建XML文件 books.xml
<?xml version="1.0" encoding="utf-8" ?>
<catalog>
  <book id="bk101">
    <author>Gambardella, Matthew</author>
    <title>XML Developer's Guide</title>
    <genre>Computer</genre>
    <price>44.95</price>
    <publish_date>2000-10-01</publish_date>
    <description>An in-depth look at creating applications with XML.</description>
  </book>
  <book id="bk102">
    <author>Ralls, Kim</author>
    <title>Midnight Rain</title>
    <genre>Fantasy</genre>
    <price>5.95</price>
    <publish_date>2000-12-16</publish_date>
    <description>A former architect battles corporate zombies.</description>
  </book>
  <!-- 更多书籍 -->
</catalog>
  1. 使用LINQ to XML查询XML文件:
using System;
using System.Linq;
using System.Xml.Linq;

class Program
{
    static void Main(string[] args)
    {
        string filePath = "books.xml";

        XDocument doc = XDocument.Load(filePath);

        var expensiveBooks = from book in doc.Descendants("book")
                             let price = decimal.Parse(book.Element("price").Value)
                             where price > 10
                             select new
                             {
                                 Title = book.Element("title").Value,
                                 Author = book.Element("author").Value,
                                 Price = price
                             };

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

高级错误处理和日志记录技巧

错误处理

在LINQ查询中,错误处理是非常重要的,特别是在处理外部数据源时。可以使用 try-catch 块来捕获和处理异常。

示例:错误处理
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        try
        {
            var evenNumbers = from n in numbers
                              where n % 0 == 0 // 这里故意制造一个除零错误
                              select n;

            foreach (var num in evenNumbers)
            {
                Console.WriteLine(num);
            }
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Error: Division by zero occurred.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
    }
}
日志记录

使用日志记录库(如 Serilog、NLog 或 log4net)可以记录查询的执行情况和潜在的错误信息。

示例:使用 Serilog 进行日志记录
  1. 安装 Serilog:

    dotnet add package Serilog
    dotnet add package Serilog.Sinks.Console
  2. 配置和使用 Serilog:

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

class Program
{
    static void Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .WriteTo.Console()
            .CreateLogger();

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

        try
        {
            var evenNumbers = from n in numbers
                              where n % 0 == 0 // 这里故意制造一个除零错误
                              select n;

            foreach (var num in evenNumbers)
            {
                Console.WriteLine(num);
            }
        }
        catch (DivideByZeroException ex)
        {
            Log.Error(ex, "Division by zero occurred.");
        }
        catch (Exception ex)
        {
            Log.Error(ex, "An error occurred.");
        }
    }
}

最后

LINQ(Language Integrated Query)是一种强大的编程模型,集成于C#和VB.NET等语言中,提供了一种统一且类型安全的方式来进行数据查询和操作。通过LINQ,开发人员可以使用类似SQL的语法查询各种数据源,包括内存集合、数据库和XML文档,从而提高代码的可读性和维护性。LINQ的延迟执行特性、丰富的标准查询操作符以及与语言的无缝集成,使其成为现代数据驱动应用开发中的重要工具。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值