前言
LINQ(Language Integrated Query,语言集成查询)是一组用于C#和VB.NET语言的扩展,它允许编写C#或者VB.NET代码,以与查询数据库相同的方式操作内存数据。 LINQ提Entity Framework技术系列之7:LINQ to Entities供了丰富的类似SQL的查询语法,功能强大且容易上手。下图汇总展示了LINQ技术的官方实现集合:
图1官方LINQ实现汇总图
正 如上图所示,LINQ to Entities 是LINQ技术在实体对象模型中的一种实现,它与LINQ to SQL以及LINQ to DataSets一起构成了LINQ to ADO.NET。LINQ to Entities可以生成eSQL,并支持使用LINQ语法对实体框架服务层进行查询。下图展示了LINQ to Entities消费实体数据模型的技术细节:
图2 LINQ to Entities与实体数据模型对应图
相关概念
在开始了解LINQ to Entities之前,需要先对.NET Framework 3.5版本后对C#语言的几个扩展特性做一些阐释,这有助于我们更容易、更深刻的理解LINQ to Entities技术的原理与实现。
一、隐式实例化局部变量
.NET Framework 3.5后,实例化局部变量语法有了新的选择:使用“var”关键字隐式类型化:
var user = new User();
隐式类型化语法与Javascript语言里声明变量的语法类似,它可以一定程度上简化实例化局部变量的语法。但如果隐式实例化的作用仅限于此,那本文不会专门提及。隐式类型化更重要的用途是用于匿名类型的实例化。
需要提醒注意的是,只有局部变量实例化允许使用隐式实例化语法。这表示私有变量实例化无法享受该福利;同时,仅声明变量或实例化对象为null也是不允许使用隐式实例化语法的。
二、对象初始化
对象初始化指在实例化对象时,即可对对象的属性进行赋值:
1 var user = new User() 2 { 3 ID = Guid.NewGuid(), 4 Account = "Apollo" 5 };
三、匿名类型
LINQ to Entities的Select可以将实体类型投影为匿名类型,所以有必要对匿名类型作简单介绍。匿名类型指的是不显示声明类型的细节,而是根据上下文环 境需要,临时声明满足需要的类型。由于该类型是临时需要的,所以不必为之命名。匿名类型的声明语法如下:
var user = new { ID = Guid.NewGuid(), Name = "Apollo" };
四、扩展方法
扩 展方法是微软为扩展已有框架而创造的一个语法糖,.NET Framework 3.5就是通过很多扩展方法实现了对.NET Framework 2.0的升级扩展的。扩展方法很神奇,被扩展的对象可以不知道扩展方法的存在,就能在行为上得到扩展。扩展方法也很蹩脚,如果使用者不知情,很可能不知道 对象具有扩展行为;抑或知道有扩展方法,但是不知道要引用哪个扩展库以使其支持扩展的行为。扩展方法的语法如下:
1 public static class UserExt 2 { 3 public static void Drink(this User user, object water) 4 { 5 … 6 } 7 }
五、Lambda表达式
Lambda 表达式是由委托以及匿名方法发展而来的,它可将表达式或代码块(匿名方法)赋给一个变量,从而以最少量的输入实现编码目的。Lambda表达式一般配合 IEnumerable<T>的静态扩展方法使用,完成对象集合的快捷查询操作。Lambda表达式的语法如下:
var user = db.Users.FirstOrDefault(o => o.Account == "Apollo");
六、标准查询操作符
System.Linq.Enumerable 静态类声明了一套标准查询操作符(Standard Query Operators,SQO)方法集合。标准查询操作符的语法和标准SQL很相似,这不是偶然,而是微软有意为之,以使熟悉SQL的程序员们更容易上手。 标准查询操作符的基本语法如下:
1 using (var db = new EntityContext()) 2 { 3 var roles = from o in db.Users 4 where o.Account == "Apollo" 5 select o.Roles; 6 … 7 }
标准查询操作符和Lambda表达式的关系非常密切。编译器会将上诉表达式转化为下列以Lambda表达式为参数的显式扩展方法调用序列:
1 using (var db = new EntityContext()) 2 { 3 var roles = db.Users.Where(o => o.Account == "Apollo").Select(o => o.Roles); 4 }
标准查询操作符
接下来,将针对数据查询操作中常用的条件查询、投影、分区、排序、分组、集合、元素、量词和聚集等标准查询操作符进行分类介绍。
一、条件操作符
条件操作符Where类似于SQL中的WHERE子句,用于实现条件查询。下列扩展方法表达式查询满足条件“角色不为空”的用户集合:
var user = db.Users.Where(o => o.Roles != null);
对应的标准查询操作符表达式为:
1 var users = from o in db.Users 2 where o.Roles != null 3 select o;
二、投影操作符
投 影操作符Select类似于SQL中的SELECT子句,将对象投影为一个匿名类型实例,用于控制指定查询迭代器显示或者处理的对象属性。另外,需要注意 的是,扩展方法表达式中的Select操作符并非必须的,省略模式下,会返回完整的被投影对象。下列扩展方法表达式将用户的帐号和密码信息投影为一个匿名 类型:
var users = db.Users.Select(o => new { o.Account, o.Password });
对应的标准查询操作符表达式为:
1 var users = from o in db.Users 2 select new { o.Account, o.Password };
三、分区操作符
分区操作符实现对象的分区操作。其中,Take操作符类似于SQL中的TOP操作符,下列扩展方法表达式返回前5个用户对象:
var users = db.Users.OrderBy(o => o.Roles.Count).Take(5);
Skip操作符用于跳过指定个数对象并返回序列中的剩余对象,下列扩展方法表达式返回除前10个用户外的剩余用户:
var users = db.Users.OrderBy(o => o.Roles.Count).Skip(10);
TakeWhile操作符用于返回条件表达式值为真时的相邻元素集合,下列扩展方法表达式返回第一个拥有3个角色的用户之前的所有用户集合:
var users = db.Users.OrderBy(o => o.Roles.Count).TakeWhile(o => o.Roles.Count == 3);
SkipWhile操作符用于跳过条件表达式值为真时的元素,并返回剩下的元素集合,下列扩展方法表达式返回第一个拥有3个角色的用户之后的所有用户集合:
var users = db.Users.OrderBy(o => o.Roles.Count).SkipWhile(o => o.Roles == 3);
四、排序操作符
排 序操作符实现对象的排序功能,包括OrderBy、OrderByDescending、ThenBy、ThenByDescending和 Reverse五个操作符。其中OrderBy操作符实现对象的升序排列,相当于SQL中的ORDER BY ASC子句,下列扩展方法表达式实现用户按拥有的角色数进行升序排列:
var users = db.Users.OrderBy(o => o.Roles.Count);
对应的标准查询操作符表达式为:
1 var users = from o in db.Users 2 orderby o.Roles.Count 3 select o;
OrderByDescending操作符实现对象的降序排列,相当于SQL中的ORDER BY DESC子句,下列扩展方法表达式实现用户按拥有的角色数进行降序排列:
var users = db.Users.OrderByDescending(o => o.Roles.Count);
对应的标准查询操作符表达式为:
1 var users = from o in db.Users 2 orderby o.Roles.Count descending 3 select o;
ThenBy、 ThenByDescending和Reverse操作符只能针对IOrderedEnumerable接口对象使用,所以一般紧跟在 OrderBy/OrderByDesending操作符方法后使用。ThenBy操作符由编译器翻译为对OrderBy操作符的再次调 用;ThenByDescending操作符由编译器翻译为对OrderByDescending操作符的再次调用;Reverse操作符实现对象的排序 反向。这里不再一一举例。
五、分组操作符
分组操作符GroupBy类似于SQL中的GROUP BY子句,实现对象的分组操作。下列扩展方法表达式实现用户对象按拥有的角色数量进行分组:
var users = db.Users.GroupBy(o => o.Roles.Count);
对应的标准查询操作符表达式为:
1 var users = from o in db.Users 2 group o by o.Roles.Count into g 3 select new { RoleCount = g.Key, Group = g };
六、集合操作符
集 合操作符包括Distinct、Union、intersect和Except四个操作符,除Distinct外,其他三个操作符都可将两个序列组合成一 个序列。Distinct操作符类似于SQL中的DISTINCT关键字,用于删除序列中具有重复值的对象。下列扩展方法表达式实现将用户角色中的重复角 色删除功能:
var roles = user.Roles.Distinct();
Union操作符类似于SQL中的UNION关键字,用于求具有同样结构的两个序列的并集。下列扩展方法表达式实现将用户1和用户2所拥有的角色组合成一个角色集合,并排除其中重复的角色:
var roles = user1.Roles.Union(user2.Roles);
Intersect操作符类似于SQL中的INTERSECT关键字,用于求具有同样结构的两个序列的交集。下列扩展方法表达式返回用户1和用户2都具有的角色集合:
var roles = user1.Roles.Intersect(user2.Roles);
Except操作符类似于SQL中的EXCEPT关键字,用于返回第一个序列中有、但第二个序列中没有的对象集合。下列扩展方法表达式返回用户1拥有,而用户2没有的角色集合:
var roles = user1.Roles.Except(user2.Roles);
七、元素操作符
元 素操作符包括两组操作符,分别是用于从一个IEnumerable<T>序列中返回满足条件的单个对象或无满足条件对象时抛异常的 First、Last和Single操作符,以及返回满足条件的单个对象或无满足条件对象时返回空对象的FirstOrDefault、 LastOrDefault和SingleOrDefault操作符。其中First和FirstOrDefault操作符用于返回第一个满足条件的对 象。下列扩展方法表达式返回第一个拥有三个角色的用户:
var user = db.Users.FirstOrDefault(o => o.Roles.Count == 3);
Last和LastOrDefault操作符用于返回最后一个满足条件的对象。下列扩展方法表达式返回最后一个拥有三个角色的用户:
var user = db.Users.LastOrDefault(o => o.Roles.Count == 3);
Single和SingleOrDefault操作符用于返回满足条件的序列中的唯一元素,如果序列中包含不止一个元素,将会抛异常。下列扩展方法表达式返回帐号为“Apollo”的唯一用户,如果有多个用户帐号都为“Apollo”,则抛异常:
var user = db.Users.SingleOrDefault(o => o.Account == "Apollo");
八、量词操作符
量词操作符包括 Any、All和Contains三个操作符,用于检查序列中是否有一些对象或所有对象满足条件。其中,Any操作符用于检查序列中是否有任何一个对象满足条件。下列扩展方法表达式当有任何一个用户拥有三个角色时返回真,否则返回假:
var result = db.Users.Any(o => o.Roles.Count == 3);
All操作符用于检查序列中是否所有对象均满足条件。下列扩展方法表达式当所有用户均拥有三个角色时返回真,否则返回假:
var result = db.Users.All(o => o.Roles.Count == 3);
Contains操作符用于检查序列中是否包含指定的对象。下列扩展方法表达式当集合中包含用户1则返回真,否则返回假:
var result = db.Users.Where(o => o.Roles.Count == 3).Contains(user1);
九、聚集操作符
聚集操作符包括Count、Min、Max、Sum和Average等多个操作符,用于对对象集合进行统计计算。其中,Count操作符类似于SQL中的COUNT关键字,用于计算序列中满足条件的对象个数。下列扩展方法表达式返回拥有3个角色的用户数量:
var result = db.Users.Count(o => o.Roles.Count == 3);
Min操作符类似于SQL中的MIN关键字,用于返回按条件计算的最小值。下列扩展方法表达式返回拥有最少角色数量的用户所拥有的角色数量:
var result = db.Users.Min(o => o.Roles.Count);
Max操作符类似于SQL中的MAX关键字,用于返回按条件计算的最大值。下列扩展方法表达式返回拥有角色数量最多的用户所拥有的角色数量:
var result = db.Users.Max(o => o.Roles.Count);
Sum操作符类似于SQL中的SUM关键字,用于返回按条件计算的总数。下列扩展方法表达式返回已赋予用户的所有角色总数:
var result = db.Users.Sum(o => o.Roles.Count);
Average操作符类似于SQL中的AVERAGE关键字,用于返回按条件计算的平均值。下列扩展方法表达式,用于返回用户所拥有的角色平均数:
var result = db.Users.Average(o => o.Roles.Count);