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


  在項目過程中,兩個實體數據之間在往往並非完全獨立的,而是存在一定的關聯關系,如一對一、一對多及多對多等關聯。存在關聯關系的實體,經常根據一個實體的實例來查詢獲取與之關聯的另外實體的實例。

  Entity Framework常用處理數據關聯加載的方式有3種:延遲加載(Lazy Loading)、貪婪加載(Eager Loading)以及顯示加載(Explicit Loading)。

  1、延遲加載(Lazy Loading)

  延遲加載是項目應用中常見的方式,Entity Framework在需要時可以自動為一個實體的實例獲取關聯的數據。

  Entity Framework自動延遲加載需要滿足的條件:

  1>、POCO類必須是public而非sealed;

  2>、集合屬性必須的Virtual修飾的,這樣Entity Framework才能Override以包含延遲加載的邏輯。

  示例:

  文件類Province.cs:

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:

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查詢被發送到數據庫中執行,數據庫進行了不必要的查詢。

  2、貪婪加載(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);

  3、顯示加載(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);
}

  4、集合屬性查詢

  在使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM