创作不易,麻烦给个免费的小心心,万分感谢~
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提供了多种连接操作符,如Join
、GroupJoin
和Left Join
等。
示例:内连接(Inner Join)
假设我们有两个表:Customers
和Orders
,我们想找出每个客户的订单信息。
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提供了多种聚合操作符,如Count
、Sum
、Min
、Max
和Average
等,用于对集合进行统计计算。
示例:计算总和和平均值
假设我们有一个包含产品价格的列表,我们想计算总价格和平均价格。
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);
}
强制立即执行
有时候我们需要强制立即执行查询,可以使用ToList
、ToArray
、ToDictionary
等方法。
示例:立即执行
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提供了强大的查询功能,但在处理大量数据时,性能优化是非常重要的。以下是一些常见的优化技巧:
- 减少数据传输量:尽量在数据库中进行过滤和聚合操作,减少传输到应用程序的数据量。
- 避免多次查询:尽量在一个查询中完成所有操作,避免多次查询数据库。
- 使用索引:确保数据库表中有适当的索引,以提高查询性能。
- 缓存结果:对于不经常变化的数据,可以考虑缓存查询结果,减少重复查询。
示例:优化查询
假设我们有一个包含大量产品的数据库表,我们想找出价格最高的前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
在处理大数据集时,AsEnumerable
和 AsQueryable
可以帮助你控制查询的执行位置。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}");
}
}
}
}
最佳实践
-
避免在查询中使用复杂的方法调用:尽量在查询外部处理复杂的逻辑,以避免影响查询性能。
-
使用分页:在处理大量数据时,使用分页可以显著提高性能。
-
缓存结果:对于不经常变化的数据,可以考虑缓存查询结果,减少重复查询。
-
使用延迟加载:在需要时才加载相关数据,可以减少初始加载的时间和资源消耗。
-
编写可读性强的代码:尽量使用查询表达式来编写可读性强的代码,必要时再使用 Lambda 表达式。
分页查询
分页查询在处理大量数据时非常重要,它可以显著提高应用程序的性能和响应速度。LINQ 提供了 Skip
和 Take
方法来实现分页。
示例:分页查询
假设我们有一个包含大量产品的数据库表,我们想分页显示每页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 文件,我们想读取它并提取特定的信息。
-
安装
FileHelpers
库:dotnet add package FileHelpers
-
使用
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 查询时,可以使用 ToList
或 ToArray
方法将查询结果转换为集合,以便在调试器中查看。
示例:调试 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 查询
-
安装 NUnit 测试框架:
dotnet add package NUnit dotnet add package NUnit3TestAdapter
-
编写单元测试:
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
库来实现动态查询。
示例:动态查询
-
安装
System.Linq.Dynamic.Core
库:dotnet add package System.Linq.Dynamic.Core
-
使用动态查询:
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
表。
-
安装 EF Core:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
-
定义数据库上下文和实体:
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;");
}
}
- 使用 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文件,我们想从中提取特定的信息。
- 创建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>
- 使用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 进行日志记录
-
安装 Serilog:
dotnet add package Serilog dotnet add package Serilog.Sinks.Console
-
配置和使用 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的延迟执行特性、丰富的标准查询操作符以及与语言的无缝集成,使其成为现代数据驱动应用开发中的重要工具。