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查詢相關的功能基本探討完了,後續博客咱們繼續探討下對實體的增刪改的基本操做。 

相關文章
相關標籤/搜索