DbContext 查询(三)

接上一篇《DbContext 查询(二)》

Eager Loading 

暂且称之为主动加载, 主动加载取决于你要告诉EF你想要加载哪些相关数据进内存,之后EF会在生成的SQL语句中使用JOIN来查询数据。让我们看如下示例:查询所有Destinations以及相关的Loadings。

Example 2-24

1     private static void TestEagerLoading()

2     {
 3           using (var context = new BreakAwayContext())
 4           {
 5                 var allDestinations = context.Destinations.Include(d => d.Lodgings);
 6 
 7                 foreach (var destination in allDestinations)
 8                 {
 9                       Console.WriteLine(destination.Name);
10 
11                       foreach (var lodging in destination.Lodgings)
12                       {
13                             Console.WriteLine(" - " + lodging.Name);
14                       }
15                 }
16           }

17     }

以上示例使用了Include方法表明查询返回的所有Destinations应该包含了相关的Lodging数据。Include使用lambda表达式来指明那个属性要包含在返回的数据中,大家查看MSDN会发现Include方法还有一个重载方法,接受的是一个字符串参数,在我们的示例中这样写Include("Lodgings"),这个重载问题重重,由于参数不是强类型的所以编译器不能编译时检查正确与否,不推荐大家使用。

在单个查询也可以包含多个相关实体数据,比如我们想查询Lodgings并且包含PrimaryContact外加关联的Photo。我们可以这样下:

context.Lodgings.Include(p=>p.PrimaryContact.Photo);

再比如你想要查询Destinations并包含Lodgings外加Lodgings相关的PrimaryContact,我们可以这么写:

context.Destinations.Include(p=>p.Lodgings.Select(t=>t.PrimaryContact));

Include方法在同一个查询中也可调用多次来指明加载不同的数据:

context.Lodgings

.Include(p=>p.PrimaryContact)

.Include(p=>p.SecondaryContact);

关于Eager Loading的一些缺点 

有上文得知,Eager Loading在生成的SQL中使用JOIN来进行查询,这会将以前需要多个查询语句才能得到的结果,到现在可能只需要一个查询语句就可以实现,但这并不总是好的。当你想要Include更多的数据,SQL语句中使用JOIN的次数也会更多,这会形成更慢以及更复杂的查询,如果你需要相当数量的关联数据,多个简单查询语句通常会比一个又大又复杂的查询语句更快。

在LINQ查询语句中使用Include

你也可在LINQ查询语句中使用Include,如果你使用query syntax:

var query = from d in context.Destinations.Include(d=>d.Lodgings)

where d.Country ==""

select d;

如果你使用method syntax,则可以这样写:

var query = context.Destinations

.Include(d=>d.Lodgings)

.Where(d=>d.Country=="");

Include是IQueryable<T>的扩展方法, 所以在查询的任何点都可以使用,并不需要立即就跟在DbSet之后,比如你想加载国家为澳大利亚的Destination的相关Lodgings:

var query = from d in context.Destinations

where d.Country = "Australia"

select d;

query = query.Include(d=>d.Lodgings);

记住Include并不是修改原有查询,而是返回一个新的查询,同时我们也强调过多次,直到有代码访问查询的结果,否则EF不会执行查询,上面的这段代码并没有使用查询返回的结果,所有EF不会执行任何查询。

Explicit Loading 

第三个加载选项是Explicit Loading。Explicit Loading类似于Lazy Loading(相关联数据是分开加载的)。当主数据被加载完,不同于Lazy Loading,Explicit Loading不会自动为你加载相关数据,你需要手动调用一个方法。

下面列出了你会使用Explicit Loading而不是Lazy Loading的一些原因:

  • 你定义的类的导航字段无需再被定义为virtual的。
  • 你对查询什么时候会被发送到数据库很清楚。Lazy Loading会潜在的生成很多的查询,而使用Explicit Loading可以很清楚的知道查询什么时候在哪被执行。

Explicit Loading是使用DbContext.Entry方法来实现的。Entry方法让你可以操作DbContext内实体的所有信息。除了可以访问存储在实体内的当前实体的信息,还可以访问实体的状体以及从数据库返回的原始实体值的信息。Entry方法还可以让你对实体调用一些操作,包括为导航字段加载数据。

一旦我们获取了一个实体,我们就可以使用Collection和Reference方法来查看实体导航字段的信息以及操作导航字段的方法。Load方法就是其中一个操作方法,而它的用法上篇博客都有讲到,这里就不再赘述。

让我们来用Explicit Loading来同样的加载名称为Grand Canyon的Destination的关联属性Lodgings:

Example 2-25

1     private static void TestExplicitLoading()

2     {
 3           using (var context = new BreakAwayContext())
 4           {
 5                 var query = from d in context.Destinations
 6                             where d.Name == "Grand Canyon"
 7                             select d;
 8 
 9                 var canyon = query.Single();
10 
11                 context.Entry(canyon)
12                   .Collection(d => d.Lodgings)
13                   .Load();
14 
15                 Console.WriteLine("Grand Canyon Lodging:");
16                 foreach (var lodging in canyon.Lodgings)
17                 {
18                     Console.WriteLine(lodging.Name);
19                 }
20           }
21     }

上面代码中,前半部分是普通的LINQ查询语句,之后调用了Entry方法,传递了canyon实例,然后调用Collection方法来操作到Lodgings导航属性,然后加载。Collection和Reference使用lambda表达式作为参数传递,类似于Include方法他们同时也有字符串参数的重载方法,孰优孰劣就不再赘述了!

如果你运行上面代码,并监控数据库查询语句,你会看到两个查询: 一个执行在代码请求名称为Grand Canyon的Destination的单个结果(Single)时,另一个运行在Load方法调用时。

你可以看到Explicit Loading可以使用在加载集合导航字段的所有内容上,而它也可以使用在加载一部分内容上(通过LINQ 查询)。

Explicit Loading(显式加载)一个导航字段(非集合)看起来非常类似,只不过调用方法变成了Reference:

var lodging = context.Lodgings.First();

context.Entry(lodging)

.Reference(p=>p.PrimaryContact)

.Load();

验证导航字段是否被加载了

调用Reference以及Collection方法之后呢,你可以访问IsLoaded属性。IsLoaded会告诉你导航字段的所有内容是否从数据库加载了。这个属性在我们使用Lazy、Eager、Explicit Loading来加载导航字段实体的时候会被设置为True。

Example 2-26

1     private static void TestIsLoaded()

2     {
 3           using (var context = new BreakAwayContext())
 4           {
 5                 var canyon = (from d in context.Destinations
 6                               where d.Name == "Grand Canyon"
 7                               select d).Single();
 8 
 9                 var entry = context.Entry(canyon);
10 
11                 Console.WriteLine("Before Load: {0}", entry.Collection(d => d.Lodgings).IsLoaded);
12 
13                 entry.Collection(d => d.Lodgings).Load();
14                 Console.WriteLine("After Load: {0}", entry.Collection(d => d.Lodgings).IsLoaded);
15           }

16     }

上面示例代码运行之后一目了然:第一次打印是False,因为还没有使用任何一种加载模式加载导航属性实体,第二次打印就变为True了。

如果你在使用Explicit Loading,如果导航属性内容可能已经被加载了,你就可以使用IsLoaded来决定是否要加载。

查询集合导航属性的内容 

到现在为止你已经知道了如何加载所有集合导航属性的内容,这样你就可以在内存中操作数据(LINQ to Object 筛选、排序等),如果你只对集合导航属性的一部分内容感兴趣,你可以只把这部分数据加载到内存中,或者你只是想要计算数量,或别的一些计算操作,你只需要把计算结果加载到内存中。

一旦你使用了Entry和Collection方法来切入到集合导航属性,之后你就可以使用Query方法来获得一个LINQ查询(导航属性的内容)。因为这是一个LINQ查询,之后你就能再进行筛选、排序、聚集等操作。

假设你想要找到距离最近的机场少于10英里的 名称为Grand Canyon的Destination相关的Lodgings。你可以写如下示例:

Example 2-27 (内存内查询导航属性)

1     private static void QueryLodgingDistance()

2     {
 3           using (var context = new BreakAwayContext())
 4           {
 5                 var canyonQuery = from d in context.Destinations
 6                                   where d.Name == "Grand Canyon"
 7                                   select d;
 8 
 9                 var canyon = canyonQuery.Single();

15                 var distanceQuery = from l in canyon.Lodgings
16                                     where l.MilesFromNearestAirport <= 10
17                                     select l;
18 
19                 foreach (var lodging in distanceQuery)
20                 {
21                     Console.WriteLine(lodging.Name);
22                 }
23           }

24     }

上面这段代码的问题在于使用LINQ to Object来查询Lodgings导航属性内容。这回导致这个属性被Lazy Loading,加载所有数据到内存中。代码之后又对数据进行了筛选,意味着并不需要加载所有数据进内存。让我们重写这段代码:Example 2-27:

1 private static void QueryLodgingDistance()

2     {
 3           using (var context = new BreakAwayContext())
 4           {
 5                 var canyonQuery = from d in context.Destinations
 6                                   where d.Name == "Grand Canyon"
 7                                   select d;
 8 
 9                 var canyon = canyonQuery.Single();
10 
11                 var lodgingQuery = context.Entry(canyon)
12                   .Collection(d => d.Lodgings)
13                   .Query();
14 
15                 var distanceQuery = from l in lodgingQuery
16                                     where l.MilesFromNearestAirport <= 10
17                                     select l;
18 
19                 foreach (var lodging in distanceQuery)
20                 {
21                     Console.WriteLine(lodging.Name);
22                 }
23           }

24     }

更新后的这段代码使用了Query方法来为Grand Canyon相关的Lodgings创建LINQ to Entities查询,然后对这查询进行筛选。下面foreach遍历distanceQuery时EF执行SQL语句转换并对MilesFromNearsAirport在数据库中进行筛选。这就意味着只有你所需要的数据被加载进了内存。

也许你想知道名称为Grand Canyon的Destinations有多少个Lodging。你可以加载所有的Lodgings然后获得个数,但为什么不仅仅只是获得一个单一的数字结果而无需加载所有数据呢,看如下示例:

Example 2-29

1     private static void QueryLodgingCount()

2     {
 3           using (var context = new BreakAwayContext())
 4           {
 5                 var canyonQuery = from d in context.Destinations
 6                                   where d.Name == "Grand Canyon"
 7                                   select d;
 8 
 9                 var canyon = canyonQuery.Single();
10 
11                 var lodgingQuery = context.Entry(canyon)
12                   .Collection(d => d.Lodgings)
13                   .Query();
14 
15                 var lodgingCount = lodgingQuery.Count();
16                 Console.WriteLine("Lodging at Grand Canyon: " + lodgingCount);
17           }

18     }

以上代码无需过多解释,Query方法返回的是LINQ to Entities查询,它意识到你只是需要数量然后把所有查询推到数据库端,所以只有一个简单的数字从数据库返回了

Explicit Loading 导航属性内容的子集 

你可以同时使用Query以及Load方法来进行筛选之后的显式加载(filtered explicit load),这个explicit loading仅仅加载导航属性内容的子集,比如你想要仅仅加载名称为Grand Canyon的Destination的相关的Lodging以及这个相关的Lodging的名称中包含“Hotel”的数据:

context.Entry(canyon)

.Collecction(p=>p.Lodgings)

.Query()

.Where(l=>l.Name.Contains("Hotel"))

.Load();

至此关于DbContext查询相关的功能基本探讨完了,后续博客我们继续探讨下对实体的增删改的基本操作。

时间: 2024-10-19 03:35:02

DbContext 查询(三)的相关文章

DbContext 查询(二)

接上一篇<DbContext 查询>. 对本地数据运行LINQ查询 由上篇博客可得知,Local属性返回的是内存中的数据集合,那使用LINQ to Object我们可以对这些数据运行查询. 查看一下示例:Example 2-21 1 private static void LocalLinqQueries() 2     { 3       using (var context = new BreakAwayContext()) 4       { 5         context.Dest

DbContext 查询

使用LINQ to Entities来写查询语句 Entity Framework查询是使用的.NET Framework功能Language Integrated Query,AKA LINQ.LINQ与.NET的编程体验是紧密集成在一起的,它提供了强类型的查询,何谓强类型,各位自行补脑.与弱类型的查询相比,它提供了编译时检查来保证你的查询通过验证以及IntelliSense. LINQ是一个通用的查询框架,并不仅仅是针对Entity Framework或者数据库的,LINQ提供程序负责把LI

子查询三(在FROM子句中使用子查询)

FROM子句中使用子查询一般都是返回多行多列,可以将其当作一张数据表 示例一.查询出每个部门的编号,名称,位置,部门人数,平均工资 SELECT d.deptno,d.dname,d.loc,temp.con,temp.avgsal FROM dept d,(SELECT deptno dno,COUNT(empno) con,ROUND (AVG(sal),2) avgsal FROM emp GROUP BY deptno) temp WHERE d.deptno=temp.dno; 示例二

sql 查询三条边是否能构成三角形

列表:A B C 代表三角形三条边 输出: Isosceles Equilateral Scalene Not A Triangle 代码如下: select CASE when (A + B) <= C then 'Not A Triangle' when A = B and B = C then 'Equilateral' when (A = B) or (B = C) or (A = C) then 'Isosceles' when A != B and B != C and A != C

常用SQL语句的整理--SQL server 2008(查询三--子查询)和guid

--分页数据----ROW_NUMBER()叫做开窗函数,可以进行分页操作 select ROW_NUMBER() over(order by id)as num,*from gb_data----给每一列加上一个连续的num值,方便取第几个到第几个数据的时候使用的 select ROW_NUMBER() over(order by id)as num,*from gb_data where num>5and num<10--这行代码是错误的,因为系统识别不出来num是多少,为什么呢? --是

10-Mysql-Ubuntu-数据表中数据的查询(三)

数据的查询(select) (1)查询整个表的数据: select  * from 表名; (2)查询给定条件的数据: select  * from 表名 where 条件; (3)查询表中某些字段: select 字段1,字段2,... from 表名; (4)查询时使用as指定字段别名: select 字段1 as 别名1, 字段2 as 别名2 from 表名;   (4)查询时更改字段显示的顺序(书写的字段顺序就是显示的顺序): select 字段1 as 别名1, 字段2 as 别名2

EasyUI datagrid 查询 三

$("#grid").datagrid("load",{  a: $('#id').val(),b :$('#text').val() });   {} 里面可以 是序列化参数 $("#grid").datagrid("reload",{ }); $("#grid").datagrid("loadData",{ "total":"30″,rows:[] })

[转载] 详述三种现代JVM语言--Groovy,Scala和Clojure

转载自http://www.tuicool.com/articles/jYzuAv和http://www.importnew.com/1537.html 在我与Martin Fowler曾经合作呈现的一次主题演讲中,他作出了一个有洞察性的观点: Java的遗产将是平台,而不是程序设计语言. Java技术的原始工程师们作出了一个明智的决定,就是将编程语言与运行时环境分开,最终这使得超过200种语言能够运行在Java平台上.这种架构对于该平台的长期活力是至关重要的,因为计算机程序设计语言的寿命一般都

python 全栈 数据库 (三) python操作数据库

python 操作MYSQL数据库主要有两种方式: 使用原生模块:pymysql ORM框架:SQLAchemy 一.pymysql 1.1下载安装模块 第一种:cmd下:执行命令下载安装:pip3 install pymysql 第二种:IDE下pycharm python环境路径下添加模块 1.2使用操作 #导入模块 import pymysql #建立连接通道,建立连接填入(连接数据库的IP地址,端口号,用户名,密码,要操作的数据库,字符编码) conn = pymysql.connect