Entity Framework加載控制Loading Entities

Entity Framework允許控制對象之間的關系,在使用EF的過程中,很多時候我們會進行查詢的操作,當我們進行查詢的時候,哪些數據會被加載到內存中呢?所有的數據都需要嗎?在一些場合可能有意義,例如:當查詢的實體僅僅擁有一個相關的子實體時可以加載所有的數據到內存中。但是,在多數情況下,你可能並不需要加載全部的數據, 而是隻要加載一部分的數據即可。

默認情況下,EF僅僅加載查詢中涉及到的實體,但是它支持兩種特性來幫助你控制加載:

  • 1、貪婪加載
  • 2、延遲加載

下面以客戶類型、客戶和客戶郵件三個實體之間的關系來講解兩種加載方式。

從上圖可以看出三個實體類之間的關系:

客戶類型和客戶是一對多的關系:一個客戶類型可以有多個客戶。
客戶和客戶郵件是一對一的關系:一個客戶隻有一個郵箱地址。(假設隻有一個郵箱地址)

一、延遲加載(Lazy Loading)

延遲加載:即在需要或者使用的時候才會加載數據。默認情況下,EF使用延遲加載的方式來加載數據。延遲加載是這樣一種過程:直到LINQ查詢的結果被枚舉時,該查詢涉及到的相關實體才會從數據庫加載。如果加載的實體包含瞭其他實體的導航屬性,那麼直到用戶訪問該導航屬性時,這些相關的實體才會被加載。

使用延遲加載必須滿足兩個條件:
1、實體類是由Public修飾符修飾的,不能是封閉類。
2、導航屬性標記為Virtual。

1、定義實體類

CustomerType實體類定義如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LazyLoding.Model
{
    public class CustomerType
    {
        public int CustomerTypeId { get; set; }
        public string Description { get; set; }

        // 導航屬性使用virtual關鍵字修飾,用於延遲加載
        public virtual ICollection<Customer> Customers { get; set; }
    }
}

Customer實體類定義如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LazyLoding.Model
{
    public class Customer
    {
        public int CustomerId { get; set; }
        public string Name { get; set; }

        // 導航屬性使用virtual關鍵字修飾,用於延遲加載
        public virtual CustomerType CustomerType { get; set; }
        // 導航屬性使用virtual關鍵字修飾,用於延遲加載
        public virtual CustomerEmail CustomerEmail { get; set; }
    }
}

CustomerEmail實體類定義如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LazyLoding.Model
{
    public class CustomerEmail
    {
        public int CustomerEmailId { get; set; }
        public string Email { get; set; }
        // 導航屬性使用virtual關鍵字修飾,用於延遲加載
        public virtual Customer Customer { get; set; }
    }
}

2、定義數據上下文類,並配置實體關系

using LazyLoding.Model;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LazyLoding.EF
{
    public class Context :DbContext
    {
        public Context()
            : base("name=AppConnection")
        {

        }

        #region 將領域實體添加到DbSet中
        public DbSet<CustomerType> CustomerTypes { get; set; }
        public DbSet<Customer> Customers { get; set; }
        public DbSet<CustomerEmail> CustomerEmails { get; set; }
        #endregion

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // 設置表名和主鍵
            modelBuilder.Entity<CustomerType>().ToTable("CustomerType").HasKey(p => p.CustomerTypeId);
            modelBuilder.Entity<Customer>().ToTable("Customer").HasKey(p => p.CustomerId);
            modelBuilder.Entity<CustomerEmail>().ToTable("CustomerEmail").HasKey(p => p.CustomerEmailId);

            // 設置實體關系
            /*
             配置一對多關系
             HasMany:表示一個CustomerType裡面包含多個Customers
             WithRequired:表示必選,CustomerType不能為空
             MapKey:定義實體之間的外鍵
             */
            modelBuilder.Entity<CustomerType>().HasMany(p => p.Customers).WithRequired(t => t.CustomerType)
                .Map(m =>
                {
                    m.MapKey("CustomerTypeId");
                });

            /*
             配置一對一的關系
             HasRequired:表示前者必選包含後者,前者可以獨立存在,後者不可獨立存在
             WithRequiredPrincipal:指明實體的主要 這裡表示指定Customer表是主表可以獨立存在
             MapKey:定義實體之間的外鍵
             */
            modelBuilder.Entity<Customer>().HasRequired(p => p.CustomerEmail).WithRequiredPrincipal(t => t.Customer)
                .Map(m =>
                {
                    m.MapKey("CustomerId");
                });
            base.OnModelCreating(modelBuilder);
        }
    }
}

3、使用數據遷移生成數據庫,並重寫Configuration類的Seed()方法填充種子數據

Configuration類定義如下:

namespace LazyLoding.Migrations
{
    using LazyLoding.Model;
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : DbMigrationsConfiguration<LazyLoding.EF.Context>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(LazyLoding.EF.Context context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method
            //  to avoid creating duplicate seed data.

            // 初始化種子數據
            context.CustomerTypes.AddOrUpdate(
                new CustomerType()
                {
                    Description = "零售",
                    Customers = new List<Customer>()
                     {
                       new Customer(){Name="小喬", CustomerEmail=new CustomerEmail(){ Email="[email protected]"}},
                       new Customer(){Name="周瑜",CustomerEmail=new CustomerEmail(){Email="[email protected]"}}
                     }
                },
                new CustomerType()
                {
                    Description = "電商",
                    Customers = new List<Customer>()
                  {
                    new Customer(){Name="張飛", CustomerEmail=new CustomerEmail(){Email="[email protected]"}},
                    new Customer(){Name="劉備",CustomerEmail=new CustomerEmail(){Email="[email protected]"}}
                  }
                }
                );
        }
    }
}

4、查看生成的數據庫

5、查看Main方法,並打開SQL Server Profiler監視器監視數據庫

// 還沒有查詢數據庫
var customerType = dbContext.CustomerTypes;

繼續執行

查看監視器:

發現這時候產生瞭查詢的SQL語句。

這就是EF的延遲加載技術,隻有在數據真正用到的時候才會去數據庫中查詢。

使用Code First時,延遲加載依賴於導航屬性的本質。如果導航屬性是virtual修飾的,那麼延遲加載就開啟瞭,如果要關閉延遲加載,不要給導航屬性加virtual關鍵字就可以瞭。

註意:如果想要為所有的實體關閉延遲加載,那麼可以在Context的構造函數中配置關閉屬性即可,代碼如下:

public Context() : base("name=AppConnection")
{
      // 配置關閉延遲加載
      this.Configuration.LazyLoadingEnabled = false;
}

二、貪婪加載(Eager Load)

貪婪加載:顧名思義就是一次性把所有數據都加載出來。貪婪加載是這樣一種過程:當我們要加載查詢中的主要實體時,同時也加載與之相關的所有實體。要實現貪婪加載,我們要使用Include()方法。

下面我們看一下如何在加載Customer數據的時候,同時也加載所有的CustomerType數據(操作此功能時暫時先關閉延遲加載以免影響)。

//貪婪加載,以下兩種方式都可以 
// 在使用Lambda表達式指明要加載的導航實體時,要引用命名空間:System.Data.Entity
var customers = dbContext.Customers.Include(p => p.CustomerType).Include(p => p.CustomerEmail).ToList();
//方式2
var query = dbContext.Customers.Include("CustomerType").Include("CustomerEmails");

總結:

貪婪加載:

  • 1、減少數據訪問的延遲,在一次數據庫的訪問中返回所有的數據。
  • 2、一次性加載所有的數據到內存中,可能導致部分數據實際用不到,從而導致讀取數據的速度變慢,效率變低。

延遲加載:

  • 1、隻在需要讀取關聯數據的時候才進行加載。每一條數據都會訪問一次數據庫,導致數據庫的壓力加大。
  • 2、可能因為數據訪問的延遲而降低性能,因為循環中,每一條數據都會訪問一次數據庫,導致數據庫的壓力增大。

如何選擇使用哪種查詢機制:

  • 1、如果是在foreach循環中加載數據,那麼使用延遲加載會比較好,因為不需要一次性將所有數據都讀取出來,這樣雖然可能會造成多次查詢數據庫,但基本上在可以接受的范圍之內。
  • 2、如果在開發時就可以預見需要一次性加載所有的數據,包含關聯表的所有數據,那麼使用貪婪加載是比較好的選擇,但是此種方式會導致效率問題,尤其是在數據量大的情況下。

代碼下載地址:點此下載

到此這篇關於Entity Framework加載控制Loading Entities的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: