在項目過程當中,兩個實體數據之間在每每並不是徹底獨立的,而是存在必定的關聯關係,如一對1、一對多及多對多等關聯。存在關聯關係的實體,常常根據一個實體的實例來查詢獲取與之關聯的另外實體的實例。sql
Entity Framework經常使用處理數據關聯加載的方式有3種:延遲加載(Lazy Loading)、貪婪加載(Eager Loading)以及顯示加載(Explicit Loading)。數據庫
一、延遲加載(Lazy Loading)ide
延遲加載是項目應用中常見的方式,Entity Framework在須要時能夠自動爲一個實體的實例獲取關聯的數據。this
Entity Framework自動延遲加載須要知足的條件:spa
1>、POCO類必須是public而非sealed;code
2>、集合屬性必須的Virtual修飾的,這樣Entity Framework才能Override以包含延遲加載的邏輯。blog
示例:內存
文件類Province.cs:ci
using System; using System.Collections.Generic; namespace Portal.Models { public class Province { public Province() { this.Cities = new List<City>(); } public int ProvinceID { get; set; } public string ProvinceNo { get; set; } public string ProvinceName { get; set; } public virtual ICollection<City> Cities { get; set; } } }
文件類City.cs:get
using System; using System.Collections.Generic; namespace Portal.Models { public class City { public int CityID { get; set; } public Nullable<int> ProvinceID { get; set; } public string CityNo { get; set; } public string CityName { get; set; } public virtual Province Province { get; set; } } }
文件類Program.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.Entity; using Portal.Models; namespace Portal { class Program { static void Main(string[] args) { using (var ctx = new PortalContext()) { var province = ctx.Provinces.Find(3); foreach (var city in province.Cities) { Console.WriteLine(city.CityName); } } } } }
以上代碼在運行以後,執行了兩條SQL語句,分別用於讀取單條Province記錄及與該條記錄相關聯的City記錄。
exec sp_executesql N'SELECT [Limit1].[ProvinceID] AS [ProvinceID], [Limit1].[ProvinceNo] AS [ProvinceNo], [Limit1].[ProvinceName] AS [ProvinceName] FROM ( SELECT TOP (2) [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[ProvinceNo] AS [ProvinceNo], [Extent1].[ProvinceName] AS [ProvinceName] FROM [dbo].[Province] AS [Extent1] WHERE [Extent1].[ProvinceID] = @p0 ) AS [Limit1]',N'@p0 int',@p0=3
exec sp_executesql N'SELECT [Extent1].[CityID] AS [CityID], [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[CityNo] AS [CityNo], [Extent1].[CityName] AS [CityName] FROM [dbo].[City] AS [Extent1] WHERE [Extent1].[ProvinceID] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=3
延遲加載的不足:
延遲加載使用簡單,應用程序不須要真正知道數據已經被從數據庫中加載出來,但只要將可能致使大量的SQL查詢被髮送到數據庫中執行,數據庫進行了沒必要要的查詢。
二、貪婪加載(Eager Loading)
貪婪加載:使用Include加載關聯的數據,在Entity Framework進行查詢時,即同時加載出關聯的數據。Entity Framework貪婪加載將使用一條JOIN的SQL語句進行查詢。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.Entity; using Portal.Models; namespace Portal { class Program { static void Main(string[] args) { using (var ctx = new PortalContext()) { var provinces = ctx.Provinces .Include(p => p.Cities); foreach (var province in provinces) { foreach (var city in province.Cities) { Console.WriteLine("{0}-{1}", province.ProvinceName, city.CityName); } } } } } }
運行代碼所執行的SQL語句:
SELECT [Project1].[ProvinceID] AS [ProvinceID], [Project1].[ProvinceNo] AS [ProvinceNo], [Project1].[ProvinceName] AS [ProvinceName], [Project1].[C1] AS [C1], [Project1].[CityID] AS [CityID], [Project1].[ProvinceID1] AS [ProvinceID1], [Project1].[CityNo] AS [CityNo], [Project1].[CityName] AS [CityName] FROM ( SELECT [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[ProvinceNo] AS [ProvinceNo], [Extent1].[ProvinceName] AS [ProvinceName], [Extent2].[CityID] AS [CityID], [Extent2].[ProvinceID] AS [ProvinceID1], [Extent2].[CityNo] AS [CityNo], [Extent2].[CityName] AS [CityName], CASE WHEN ([Extent2].[CityID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1] FROM [dbo].[Province] AS [Extent1] LEFT OUTER JOIN [dbo].[City] AS [Extent2] ON [Extent1].[ProvinceID] = [Extent2].[ProvinceID] ) AS [Project1] ORDER BY [Project1].[ProvinceID] ASC, [Project1].[C1] ASC
Include語句能夠在一次查詢中使用屢次。
ctx.Categories .Include(c => c.Products) .Include(c => c.News);
貪婪加載的不足:
貪婪加載的優點在於僅執行1次SQL查詢即返回所須要的結果。但使用JOIN查詢在數據庫記錄條數較多時,多條簡單的SQL查詢每每比一條複雜的JOIN查詢效率要好。
使用Include的LINQ查詢
var provinces = ctx.Provinces .Include(p => p.Cities) .Where(p => p.ProvinceID > 10);
var provinces = from p in ctx.Provinces.Include(p => p.Cities) where p.ProvinceID > 10 select p;
var expr = from p in ctx.Provinces where p.ProvinceID > 10 select p; var provinces = expr.Include(p => p.Cities);
三、顯示加載(Explicit Loading)
顯示加載與延遲加載同樣,採用主數據與關聯數據獨立分開加載。顯示加載與延遲加載的區別在於顯示加載不會自動的加載關聯數據,須要調用方法去加載。
顯示加載是使用DbContext.Entry方法來實現的,Entry方法能夠獲取DbContext中的實體信息。在使用Entry獲取實體信息以後,可使用Collection或Reference方法獲取和操做實體關聯的集合屬性。如使用Load方法查詢集合屬性。
示例1:顯示加載,使用Collection獲取集合屬性
using (var ctx = new PortalContext()) { var province = ctx.Provinces.Find(3); ctx.Entry(province) .Collection(p => p.Cities) .Query() .Load(); foreach (var city in province.Cities) { Console.WriteLine("{0}-{1}", province.ProvinceName, city.CityName); } }
上面的代碼運行以後,執行的SQL語句:
exec sp_executesql N'SELECT [Limit1].[ProvinceID] AS [ProvinceID], [Limit1].[ProvinceNo] AS [ProvinceNo], [Limit1].[ProvinceName] AS [ProvinceName] FROM ( SELECT TOP (2) [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[ProvinceNo] AS [ProvinceNo], [Extent1].[ProvinceName] AS [ProvinceName] FROM [dbo].[Province] AS [Extent1] WHERE [Extent1].[ProvinceID] = @p0 ) AS [Limit1]',N'@p0 int',@p0=3
exec sp_executesql N'SELECT [Extent1].[CityID] AS [CityID], [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[CityNo] AS [CityNo], [Extent1].[CityName] AS [CityName] FROM [dbo].[City] AS [Extent1] WHERE [Extent1].[ProvinceID] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=3
從代碼運行所執行的SQL語句能夠看出,其查詢數據庫的方式與延遲加載是相同的。
示例2:顯示加載,使用Reference方法獲取引用屬性
using (var ctx = new PortalContext()) { var city = ctx.Cities.Find(10); ctx.Entry(city).Reference(c => c.Province); Console.WriteLine("{0}-{1}", city.Province.ProvinceName, city.CityName); }
上面的代碼運行以後執行的SQL語句:
exec sp_executesql N'SELECT [Limit1].[CityID] AS [CityID], [Limit1].[ProvinceID] AS [ProvinceID], [Limit1].[CityNo] AS [CityNo], [Limit1].[CityName] AS [CityName] FROM ( SELECT TOP (2) [Extent1].[CityID] AS [CityID], [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[CityNo] AS [CityNo], [Extent1].[CityName] AS [CityName] FROM [dbo].[City] AS [Extent1] WHERE [Extent1].[CityID] = @p0 ) AS [Limit1]',N'@p0 int',@p0=10
exec sp_executesql N'SELECT [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[ProvinceNo] AS [ProvinceNo], [Extent1].[ProvinceName] AS [ProvinceName] FROM [dbo].[Province] AS [Extent1] WHERE [Extent1].[ProvinceID] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=3
檢查集合屬性是否已經加載:
using (var ctx = new PortalContext()) { var province = ctx.Provinces.Find(3); Console.WriteLine("Before load:{0}", ctx.Entry(province).Collection(p => p.Cities).IsLoaded); ctx.Entry(province) .Collection(p => p.Cities) .Load(); Console.WriteLine("After load:{0}", ctx.Entry(province).Collection(p => p.Cities).IsLoaded); }
四、集合屬性查詢
在使用Entry和Collection方法獲取到實體集合屬性以後,可使用Query方法對集合屬性進行查詢。
示例:從內存中查詢集合屬性
using (var ctx = new PortalContext()) { var province = ctx.Provinces.Find(5); var cities = from c in province.Cities where c.CityID > 30 select c; foreach (var city in cities) { Console.WriteLine("{0}-{1}", city.CityID, city.CityName); } }
代碼運行以後執行的SQL語句:
exec sp_executesql N'SELECT [Limit1].[ProvinceID] AS [ProvinceID], [Limit1].[ProvinceNo] AS [ProvinceNo], [Limit1].[ProvinceName] AS [ProvinceName] FROM ( SELECT TOP (2) [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[ProvinceNo] AS [ProvinceNo], [Extent1].[ProvinceName] AS [ProvinceName] FROM [dbo].[Province] AS [Extent1] WHERE [Extent1].[ProvinceID] = @p0 ) AS [Limit1]',N'@p0 int',@p0=5
exec sp_executesql N'SELECT [Extent1].[CityID] AS [CityID], [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[CityNo] AS [CityNo], [Extent1].[CityName] AS [CityName] FROM [dbo].[City] AS [Extent1] WHERE [Extent1].[ProvinceID] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=5
從對City表執行的SQL語句能夠看出,其並對加入查詢條件,僅只是對以前經過延長加載方式將Province實體的Cities集合屬性載人到內存中,而後經過對內存中的Cities數據進行內存查詢,並未生成新的包含查詢條件的SQL語句。
示例:在數據庫中查詢集合屬性
using (var ctx = new PortalContext()) { var province = ctx.Provinces.Find(5); var expr = ctx.Entry(province) .Collection(p => p.Cities) .Query(); var cities = from c in expr where c.CityID > 30 select c; foreach (var city in cities) { Console.WriteLine("{0}-{1}", city.CityID, city.CityName); } }
代碼運行以後執行的SQL語句:
exec sp_executesql N'SELECT [Limit1].[ProvinceID] AS [ProvinceID], [Limit1].[ProvinceNo] AS [ProvinceNo], [Limit1].[ProvinceName] AS [ProvinceName] FROM ( SELECT TOP (2) [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[ProvinceNo] AS [ProvinceNo], [Extent1].[ProvinceName] AS [ProvinceName] FROM [dbo].[Province] AS [Extent1] WHERE [Extent1].[ProvinceID] = @p0 ) AS [Limit1]',N'@p0 int',@p0=5
exec sp_executesql N'SELECT [Extent1].[CityID] AS [CityID], [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[CityNo] AS [CityNo], [Extent1].[CityName] AS [CityName] FROM [dbo].[City] AS [Extent1] WHERE ([Extent1].[ProvinceID] = @EntityKeyValue1) AND ([Extent1].[CityID] > 30)',N'@EntityKeyValue1 int',@EntityKeyValue1=5
集合屬性Count查詢
using (var ctx = new PortalContext()) { var province = ctx.Provinces.Find(5); var expr = ctx.Entry(province) .Collection(p => p.Cities) .Query(); Console.WriteLine(expr.Count()); }
代碼運行生成的SQL語句:
exec sp_executesql N'SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT COUNT(1) AS [A1] FROM [dbo].[City] AS [Extent1] WHERE [Extent1].[ProvinceID] = @EntityKeyValue1 ) AS [GroupBy1]',N'@EntityKeyValue1 int',@EntityKeyValue1=5
顯示加載集合屬性的子集:
using (var ctx = new PortalContext()) { var province = ctx.Provinces.Find(5); ctx.Entry(province) .Collection(p => p.Cities) .Query() .Where(c => c.CityNo.Contains("3")) .Load(); }
代碼運行後生成的SQL語句:
exec sp_executesql N'SELECT [Extent1].[CityID] AS [CityID], [Extent1].[ProvinceID] AS [ProvinceID], [Extent1].[CityNo] AS [CityNo], [Extent1].[CityName] AS [CityName] FROM [dbo].[City] AS [Extent1] WHERE ([Extent1].[ProvinceID] = @EntityKeyValue1) AND ([Extent1].[CityNo] LIKE N''%3%'')',N'@EntityKeyValue1 int',@EntityKeyValue1=5