DbContext 查询(三)

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

Eager Loading spa

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

  Example 2-24code

   1     private static void TestEagerLoading()blog

 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。咱们能够这样下:ci

  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查询相关的功能基本探讨完了,后续博客咱们继续探讨下对实体的增删改的基本操做。 

相关文章
相关标签/搜索