Linq 是语言集成查询(Language-Integrated Query)的简称,是一系列直接将查询功能集成到 C# 语言的技术统称。Linq 提供了统一种跨数据源和数据格式使用数据的一致模型,并且 Linq 查询支持编译时类型检查和智能提示。
支持 Linq 查询的对象有:
public class Master
{
public long Id { get; set; }
public string Name { get; set; }
}
public class Dog
{
public long Id { get; set; }
public long MasterId { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
初始化数据:
Master m1 = new Master { Id = 1, Name = " 小七" };
Master m2 = new Master { Id = 2, Name = " 安" };
Master m3 = new Master { Id = 3, Name = " 小Q" };
Dog d1 = new Dog { Id = 1, MasterId = 1, Name = " 旺财", Age = 3 };
Dog d2 = new Dog { Id = 2, MasterId = 1, Name = " 汪汪", Age = 1 };
Dog d3 = new Dog { Id = 3, MasterId = 2, Name = " 京巴", Age = 5 };
Dog d4 = new Dog { Id = 4, MasterId = 3, Name = " 泰迪", Age = 2 };
Dog d5 = new Dog { Id = 5, MasterId = 3, Name = " 中华田园", Age = 6 };
Master[] masters = { m1, m2, m3 };
Dog[] dogs = { d1, d2, d3, d4, d5 };
其中 masters 和 dogs 就是数据源,因为数组实现了 IEnumerable
在 Linq 查询中,首先是指定数据源。使用 from
子句引入数据源 masters
并声明范围变量 m
。
var masterQuery1 = from m in masters
select m;
范围变量类似 foreach
循环中的迭代变量,但查询表达式中不会真正发生迭代,当执行查询时,范围变量将充当对 masters
中每个连续元素的引用。而且无需显示指定范围变量的类型,因为编译器可以推断出范围变量的类型。
筛选器实际指定要从源数据中筛选出哪些元素。并且可以使用 C# 逻辑 AND
和 OR
运算符,在 where
子句根据需要应用尽可能多的筛选器表达式。
var masterQuery2 = from m in masters
where m.Name == "小Q"
select m;
// lambda 写法,因为编译后是同一个东西,所以可以混用
var masterQuery3 = (from m in masters select m).Where(m => m.Name == "小Q");
排序通过 orderby
子句,根据要排序类型的默认比较器,对返回序列中的元素排序。
var dogQuery1 = from d in dogs
where d.MasterId == 3 && d.Id > 3
orderby d.Name descending
select d;
var dogQuery2 = dogs.Where(d => d.MasterId == 3 && d.Id > 3).OrderByDescending(d => d.Name);
group
子句根据指定属性的结果进行分组。分组结果以列表的形式展示。列表中每个元素都是具有 key
成员对象,列表中的元素根据该属性被分组。而且在循环访问生成组序列的查询时,必须使用嵌套 foreach
循环,外层循环访问每个组,内层循环访问每个组的成员。
var dogQuery3 = from d in dogs
where d.Id > 1
group d by d.MasterId into dogGroup
where dogGroup.Key >= 2
orderby dogGroup.Key descending
select dogGroup;
var dogQuery4 = dogs.Where(d => d.Id > 1).GroupBy(d => d.MasterId)
.Where(g => g.Key >= 2).OrderByDescending(g => g.Key);
foreach (IGrouping<long, Dog> dogGroup in dogQuery3)
{
foreach (Dog dog in dogGroup)
{
Console.WriteLine(dog.Name);
}
}
联接操作用来在不同范围变量间创建关联,这里范围变量来自不同数据源。在 Linq 中,join
子句始终作用于对象集合,而非直接作用于数据库表。
var dogQuery5 = from d in dogs
join m in masters on d.MasterId equals m.Id
select new { DogName = d.Name, MasterName = m.Name };
在 Linq中不用像在 SQL 中频繁使用 join
,因为 Linq 中的外键在对象模型中表示为包含项集合的属性。
public class Master
{
public IEnumerable<Dog> Dogs { get; set; }
}
var dogQuery6 = from dog in m1.Dogs select dog;
select
子句用于指定生成的查询结果,可以是整个对象、其中一个成员、成员的子集,还可以是基于计算或新对象创建的结果类型。如上例的:…… select new { DogName = d.Name, MasterName = m.Name };
在 Linq 中,查询变量本身只存储查询命令,不执行任何操作并且不会返回任何数据。查询实际执行将推迟在 foreach
语句中循环访问查询变量之后进行,也就是在要用到具体数据的时候才会加载到内存中,而不是直接将数据查询出来。这个概念称为延迟加载,又称为懒加载。
对一系列源元素执行聚合函数的查询必须首先循环访问这些元素,如:Count
、Max
、Average
和 First
等。由于查询本身必须遍历以返回结果,所以这些查询在执行时不使用显示 foreach
语句。
int masterCount = masterQuery1.Count();
如果要强制立即执行查询并缓存其结果可以通过 ToList 或 ToArray 方法(实际内部也是遍历了查询):
List<Master> master =
(from m in masters where m.Name == "小Q" select m)
.ToList();
只有Where
、Select
、OrderBy
、GroupBy
、Join
等能用 Linq 写法,如果用Max
、Min
、Count
、Average
、Sum
、Any
、First
、FistOrDefault
、Single
、SingleOrDefault
、Distinct
、Skip
、Take
等则要用 lambda 的写法。
Any()
判断集合是否包含元素,返回值是 bool,效率一般比Count()>0
高;
Distinct()
剔除完全重复数据。自定义对象的 Equals 问题:需要重写 Equals 和 GetHashCode 方法来进行内容比较。
OrderBy()
升序、OrderByDescending()
降序、ThenBy()
指定多个排序规则、也支持ThenByDescending()
。这些操作不会影响原始的集合数据。
Ship(n)
跳过前面 n 条数据;
Take(n)
获取最多 n 条数据,如果不足 n 条获取全部;
Except(items)
排除当前集合中在 items 中存在的元素;
Union(items)
把当前集合和 items 集合组合;
Intersect(items)
把当前集合和 items 集合取交集;
GroupBy()
分组之后可以使用一些聚合函数:Average()
、Max()
、Min()
、Sum()
等;
SelectMany()
把集合中每个对象的集合属性的值重新拼接为一个新的集合;
Join()
可以实现和数据库一样的 Join 效果。