Entity Framework Code First實體關聯數據加載

  在項目過程當中,兩個實體數據之間在每每並不是徹底獨立的,而是存在必定的關聯關係,如一對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; }
    }
}
View Code

  文件類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; }
    }
}
View Code

  文件類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);
                }
            }
        }
    }
}
View Code

  以上代碼在運行以後,執行了兩條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);
                    }
                }
            }
        }
    }
}
View Code

  運行代碼所執行的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
相關文章
相關標籤/搜索