《Entity Framework 6 Recipes》中文翻譯系列 (28) ------ 第五章 加載實體和導航屬性之測試實體是否加載與顯式加載關聯實體


翻譯的初衷以及為什么選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇

5-11  測試實體引用或實體集合是否加載

問題

  你想測試關聯實體或實體集合是否已經加載到上下文中,另外你想使用Code-First來管理數據訪問。

解決方案

  假設你有如圖5-26所示的概念模型

圖5-26 一個包含projects,managers和contractors的模型

 

 在Visual Studio中添加一個名為Recipe11的控制台應用,並確保引用了實體框架6的庫,NuGet可以很好的完成這個任務。在Reference目錄上右鍵,並選擇 Manage NeGet Packages(管理NeGet包),在Online頁,定位並安裝實體框架6的包。這樣操作后,NeGet將下載,安裝和配置實體框架6的庫到你的項目中。

  接下來我們創建三個實體對象:Contractor,Manager,Project,復制代碼清單5-27中的屬性到這三個類中。

代碼清單5-27. 實體類

 public class Contractor
    {
        public int ContracterID { get; set; }
        public string Name { get; set; }
        public int ProjectID { get; set; }

        public virtual Project Project { get; set; }
    }

  public class Manager
    {
        public Manager()
        {
            Projects = new HashSet<Project>();
        }

        public int ManagerID { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Project> Projects { get; set; }
    }


 public class Project
    {
        public Project()
        {
            Contractors = new HashSet<Contractor>();
        }

        public int ProjectID { get; set; }
        public string Name { get; set; }
        public int ManagerID { get; set; }

        public virtual ICollection<Contractor> Contractors { get; set; }
        public virtual Manager Manager { get; set; }
    }

接下來,創建一個名為Recipe11Context的類,並將代碼清單5-24中的代碼添加到其中,並確保其派生到DbContext類。

代碼清單5-28. 上下文

 1  public class Recipe11Context : DbContext
 2     {
 3         public Recipe11Context()
 4             : base("Recipe11ConnectionString")
 5         {
 6             //禁用實體框架的模型兼容
 7             Database.SetInitializer<Recipe11Context>(null);
 8         }
 9 
10         public DbSet<Contractor> Contractors { get; set; }
11         public DbSet<Manager> Managers { get; set; }
12         public DbSet<Project> Projects { get; set; }
13 
14         protected override void OnModelCreating(DbModelBuilder modelBuilder)
15         {
16             modelBuilder.Entity<Contractor>().ToTable("Chapter5.Contractor");
17             modelBuilder.Entity<Manager>().ToTable("Chapter5.Manager");
18             modelBuilder.Entity<Project>().ToTable("Chapter5.Project");
19 
20             // 顯示映射實體鍵
21             modelBuilder.Entity<Contractor>().HasKey(x => x.ContracterID);
22         }
23     }

接下來添加App.Config文件到項目中,並使用代碼清單5-25中的代碼添加到文件的ConnectionStrings小節下。

代碼清單5-29. 連接字符串

復制代碼
<connectionStrings>
<add name="Recipe11ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
復制代碼

  實體框架公布了一個IsLoaded屬性。只要它100%的確定,指定的實體或實體集合全部已經加載且在上下文中有效時,它的值便為True。圖5-26中的模型表示,項目(Project),項目的管理者(manager)以及項目的承包商(Contractors)。按代碼清單5-30所示,測試關聯實體是否加載到上下文對象中。

代碼清單5-30.使用IsLoaded判斷一個實體或實體集合是否已加載到上下文中

using (var context = new Recipe11Context())
            {
                var man1 = new Manager {Name = "Jill Stevens"};
                var proj = new Project {Name = "City Riverfront Park", Manager = man1};
                var con1 = new Contractor {Name = "Robert Alvert", Project = proj};
                var con2 = new Contractor {Name = "Alan Jones", Project = proj};
                var con3 = new Contractor {Name = "Nancy Roberts", Project = proj};
                context.Projects.Add(proj);
                context.SaveChanges();
            }

            using (var context = new Recipe11Context())
            {
                var project = context.Projects.Include("Manager").First();

                if (context.Entry(project).Reference(x => x.Manager).IsLoaded)
                    Console.WriteLine("Manager entity is loaded.");
                else
                    Console.WriteLine("Manager entity is NOT loaded.");

                if (context.Entry(project).Collection(x => x.Contractors).IsLoaded)
                    Console.WriteLine("Contractors are loaded.");
                else
                    Console.WriteLine("Contractors are NOT loaded.");
                Console.WriteLine("Calling project.Contractors.Load()...");

                context.Entry(project).Collection(x => x.Contractors).Load();

                if (context.Entry(project).Collection(x => x.Contractors).IsLoaded)
                    Console.WriteLine("Contractors are now loaded.");
                else
                    Console.WriteLine("Contractors failed to load.");
            }

            Console.WriteLine("Press <enter> to continue...");
            Console.ReadLine();

代碼清單5-30的輸出如下:

Manager entity is loaded.
Contractors are NOT loaded.
Calling project.Contractors.Load()...
Contractors are now loaded.

原理

  我們使用Include()方法,從數據庫中預先加載Project實體和與它關聯的Manager。

  查詢之后,我們通過reference()方法獲取關聯實體Manager的引用和檢查IsLoaded屬性的值,來判斷manager實例是否加載。因為這是一個實體引用(引用一個單獨的父實體),調用Entry()方法返回DbEntityEntry類型上的Reference方法,它返回類型上IsLoaded屬性有效。因為我已經加載了Projects和Manager,所以,該屬性返回True.

  接下來我們檢查Contractor實體集合是否加載。因為我們沒有在Include()方法中預先加載它,也沒有使用Load()方法直接加載過它,所以它沒有被加載。只要一加載它。IsLoaded屬性便會被設置為True.

  默認行為,延遲加載,開啟時,當關聯實體或實體集合被引用時,IsLoaded屬性便會被設置為True。延遲加載會讓實體框架在實體或實體集合被引用時,自動加載。顯式加載和延遲加載有些類似,只是它不是自動的。開發人員必須顯式地使用Load()方法來加載實體,它讓開發人員可以完全自己控制是否加載,何時加載關聯實體。

  IsLoaded確切的含義,比看起來更讓人迷惑。IsLoaded被調用Load()方法的查詢設置,也被隱式的關系跨度設置。當你查詢一個實體時,會隱式查詢關聯實體Key。如果這個隱式查詢的結果是一個null值,IsLoaded會被設置成True,意思是數據庫沒有關聯的實體。當我們顯示加載關系並發現沒有關聯實體時,IsLoaded同樣會被設置成True. (譯注:這里可能會有點難理解,因為涉及到了一個術語:關系跨度(Relationship Span)的理解,它是指EF加載實體時總是一起返回外鍵值,以此來避免一些列的問題)

 

5-12  顯示加載關聯實體

問題

  你想直接加載關聯實體,不依賴默認的延遲加載功能。

解決方案

  假設你有如圖5-27所示的概念模型

圖5-27 一個包含實體 doctor、appointment、patient的模型

   圖5-27描述的模型,表示醫生(doctor)和他的患者(patient),以及預約(appointment)。代碼清單5-31,顯示加載關聯實體。

代碼清單5-31. 使用Load()方法

  1  using (var context = new EFRecipesEntities())
  2             {
  3                 var doc1 = new Doctor { Name = "Joan Meyers" };
  4                 var doc2 = new Doctor { Name = "Steven Mills" };
  5                 var pat1 = new Patient { Name = "Bill Rivers" };
  6                 var pat2 = new Patient { Name = "Susan Stevenson" };
  7                 var pat3 = new Patient { Name = "Roland Marcy" };
  8 
  9                 var app1 = new Appointment
 10                     {
 11                         Date = DateTime.Today,
 12                         Doctor = doc1,
 13                         Fee = 109.92M,
 14                         Patient = pat1,
 15                         Reason = "Checkup"
 16                     };
 17                 var app2 = new Appointment
 18                     {
 19                         Date = DateTime.Today,
 20                         Doctor = doc2,
 21                         Fee = 129.87M,
 22                         Patient = pat2,
 23                         Reason = "Arm Pain"
 24                     };
 25                 var app3 = new Appointment
 26                     {
 27                         Date = DateTime.Today,
 28                         Doctor = doc1,
 29                         Fee = 99.23M,
 30                         Patient = pat3,
 31                         Reason = "Back Pain"
 32                     };
 33 
 34                 context.Appointments.Add(app1);
 35                 context.Appointments.Add(app2);
 36                 context.Appointments.Add(app3);
 37 
 38                 context.SaveChanges();
 39             }
 40 
 41             using (var context = new EFRecipesEntities())
 42             {
 43                 // 禁用延遲加載,因為我們要顯式加載子實體
 44                 context.Configuration.LazyLoadingEnabled = false;
 45 
 46                 var doctorJoan = context.Doctors.First(o => o.Name == "Joan Meyers");
 47 
 48                 if (!context.Entry(doctorJoan).Collection(x => x.Appointments).IsLoaded)
 49                 {
 50                     context.Entry(doctorJoan).Collection(x => x.Appointments).Load();
 51                     Console.WriteLine("Dr. {0}'s appointments were explicitly loaded.",
 52                                       doctorJoan.Name);
 53                 }
 54 
 55                 Console.WriteLine("Dr. {0} has {1} appointment(s).",
 56                                   doctorJoan.Name,
 57                                   doctorJoan.Appointments.Count());
 58 
 59                 foreach (var appointment in context.Appointments)
 60                 {
 61                     if (!context.Entry(appointment).Reference(x => x.Doctor).IsLoaded)
 62                     {
 63                         context.Entry(appointment).Reference(x => x.Doctor).Load();
 64                         Console.WriteLine("Dr. {0} was explicitly loaded.",
 65                                           appointment.Doctor.Name);
 66                     }
 67                     else
 68                         Console.WriteLine("Dr. {0} was already loaded.",
 69                                           appointment.Doctor.Name);
 70                 }
 71 
 72                 Console.WriteLine("There are {0} appointments for Dr. {1}",
 73                                   doctorJoan.Appointments.Count(),
 74                                   doctorJoan.Name);
 75 
 76                 doctorJoan.Appointments.Clear();
 77 
 78                 Console.WriteLine("Collection clear()'ed");
 79                 Console.WriteLine("There are now {0} appointments for Dr. {1}",
 80                                   doctorJoan.Appointments.Count(),
 81                                   doctorJoan.Name);
 82 
 83                 context.Entry(doctorJoan).Collection(x => x.Appointments).Load();
 84 
 85                 Console.WriteLine("Collection loaded()'ed");
 86 
 87                 Console.WriteLine("There are now {0} appointments for Dr. {1}",
 88                                   doctorJoan.Appointments.Count().ToString(),
 89                                   doctorJoan.Name);
 90 
 91                 //目前,DbContext 沒有API去刷新實體,但底層的ObjectContext有,執行下面的動作。
 92                 var objectContext = ((IObjectContextAdapter)context).ObjectContext;
 93                 var objectSet = objectContext.CreateObjectSet<Appointment>();
 94                 objectSet.MergeOption = MergeOption.OverwriteChanges;
 95                 objectSet.Load();
 96 
 97                 Console.WriteLine("Collection loaded()'ed with MergeOption.OverwriteChanges");
 98 
 99                 Console.WriteLine("There are now {0} appointments for Dr. {1}",
100                                   doctorJoan.Appointments.Count(),
101                                   doctorJoan.Name);
102             }
103 
104 
105             //演示先加載部分實體集合,然后再加載剩下的
106             using (var context = new EFRecipesEntities())
107             {
108                 // 禁用延遲加載,因為我們要顯式加載子實體
109                 context.Configuration.LazyLoadingEnabled = false;
110 
111                 //加載第一個doctor然后只附加一個appointment
112                 var doctorJoan = context.Doctors.First(o => o.Name == "Joan Meyers");
113                 context.Entry(doctorJoan).Collection(x => x.Appointments).Query().Take(1).Load();
114 
115                 //注意,這里IsLoaded返回False,因為所有的實體還沒有被加載到上下文
116                 var appointmentsLoaded = context.Entry(doctorJoan).Collection(x => x.Appointments).IsLoaded;
117 
118                 Console.WriteLine("Dr. {0} has {1} appointments loaded.",
119                                   doctorJoan.Name,
120                                   doctorJoan.Appointments.Count());
121 
122                 //當我需要加載剩下的appointments,只需要簡單的調用Load()來加載它們
123                 context.Entry(doctorJoan).Collection(x => x.Appointments).Load();
124                 Console.WriteLine("Dr. {0} has {1} appointments loaded.",
125                                   doctorJoan.Name,
126                                   doctorJoan.Appointments.Count());
127             }
128 
129             Console.WriteLine("Press <enter> to continue...");
130             Console.ReadLine();

代碼清單5-31的輸出如下:

Dr. Joan Meyers's appointments were explicitly loaded.
Dr. Joan Meyers has 2 appointment(s).
Dr. Joan Meyers was already loaded.
Dr. Steven Mills was explicitly loaded.
Dr. Joan Meyers was already loaded.
There are 2 appointments for Dr. Joan Meyers
Collection clear()'ed
There are now 0 appointments for Dr. Joan Meyers
Collection loaded()'ed
There are now 0 appointments for Dr. Joan Meyers
Collection loaded()'ed with MergeOption.OverwriteChanges
There are now 2 appointments for Dr. Joan Meyers
Dr. Joan Meyers has 1 appointments loaded.
Dr. Joan Meyers has 2 appointments loaded.
Press <enter> to continue...

譯注:書的結果有誤,這是我(付燦)運行示例后的輸出。

原理

  插入一些簡單的數據到數據庫之后,我們顯式地禁用了延遲加載特征,因為我們要顯式控制關聯實體的加載。有兩種方式禁用延遲加載:

    1、設置Context.Configuration對象的LazyLoadingEnabled屬性為False。它會禁用上下文中所有實體對象的延遲加載。

    2、在每個實體類中移除導航屬性的virtual修飾關鍵字。這種方法會禁用相應實體的延遲加載,這樣就能讓你顯式控制延遲加載

  我們先獲取一個Doctor實體。如果你使用了顯式加載,這將是使用IsLoaded屬性檢查關聯實體或實體集合是否加載的一個好實踐。在代碼中,我們檢查doctor對象的appointments是否加載。如果沒有,我們使用Load()方法加載它們。

  在foreach循環中,我們枚舉了appointments,檢查與它關聯的doctor是否加載。注意輸出,這時只有一個醫生被加載,別的沒有被加載。這是因為我們的第一個查詢只獲取了一個doctor。在獲取appointmetns的過程中,實體框架會連接醫生(doctor)和他的預約(appintments),這個過程被稱為(非正式的)Relationship fixup(譯注:這些概念雖然已經產生很多年,但中文資料關於它的介紹幾乎沒有,只看到一位兄弟把它翻譯為“關系建立”,個人覺得它能表達這個詞的含義,就借用了)。Relationship fixup 不會建立好所有的關聯,特別是多對關聯的實體

  我們打印出doctor關聯實體集合appointments已加載的數量。然后我們調用Clear()方法,清空doctorJoan關聯實體集合。這個方法會清除掉doctorJoan和appointments間的關系。有趣的是,它並不會把實例從內存中移除;這些實例仍在上下文中--它們只是不在跟Doctor實體連接

  令人奇怪的是,調用Load()方法重新加載appointemts后,從輸出我們看到,doctorJoan的關聯集合沒有對象。發生了什么呢?原來是因為Load()方法需要使用一個控制如何加載實例進入上下文的參數。該參數的默認值是MergeOption.AppendOnly,它只是簡單地把不存在上下文中的實體對象加載到上下文中。在我們示例中,沒有沒有真正地把實體對象從上下文中移除。在使用Clear()方法時,只是將實體對象從關聯集合中移除,而沒有從上下文中移除。當我們使用Load()方法重新加載時,由於使用了默認的MergeOption.AppendOnly選項,又沒有發現新的實例,所有沒有實體對象被加載到上下文中(譯注:關聯實體集合自然也不會添加,但注意這里的Load()方法是生成了SQL查詢語句,產生了數據庫交互,並從數據庫獲取了相應的數據的)。其它的合並選項包含:NoTracking,OverwriteChanges,和PreserveChages。當我們使用OverwriteChanges選項時,appointments出現在Doctor實體對象的關聯集合Appointments中了。

  注意,我們在代碼中是如何進入底層,通過ObjectContext上下文對象,訪問實體框架中MergeOption行為的。MergeOption在DbContext中不被直接支持。你可能會回想起,我們使用實體框架時,有兩個上下文對象可以使用。在實體框架6中,首選是使用DbContext。它提供了直觀,易於使用的,遺留ObectContext上下文對象的外觀模式。如代碼中所示,可以通過顯式轉換,仍然可以使用ObjectContext。

  與AppendOnly一起,MegeOption類型公布了另外三個選項:

    1、NoTracking選項會關閉加載實例的對象狀態跟蹤。使用NoTracking選項,實體框架將不再跟蹤對象的變化,同時也不再知道對象是否已經加載到上下文中。如果對象使用NoTracking選項加載,那么它可以被用於對象的導航屬性上。NoTracking有一個額外的副作用。如果我們使用NoTracking選項加載一個Doctor實體,那么,使用Load()方法加載appointments時,不管默認行為AppendOnly,仍然會使用NoTracking。

    2、OverwriteChanges選項會使用從數據庫獲取的數據更新當前對象的值,實體框架會繼續使用同一個實體對象。這個選項在你想放棄上下文中對實體對象的修改,並使用數據庫中的數據來刷新它時特別管用。這個選項非常有用,例如,你的應用正在實現一個撤消操作的功能。

    3、PreserveChanges選項,本質上是OverwriteChanges選項的對立選項。當數據庫中有改變時,它會更新實體對象的值。但是當內存里的值發生改變時,它不會更新實體對象的值。一個實體對象在內存中被修改,它不會被刷新。更准確地說,在內存中修改實體對象時,它的當前值(cruuent value)不會改變,但是,如果數據庫有改變時,它的初始值(original value)會被更新。

  當你使用Load()方法時,這里有一些限制。實體狀態為Added,Deleted,或者是Detached時,不能調用Load()方法

  無論在什么時候,只要想限制關聯實體集合中實體的加載數量,Load()方法對性能的提升都有幫助。例如,我們的醫生有大量的預約,但是在很多時候,他只能處理一部分。在極罕見的情況下會處理整個集合,我可以簡單的調用Load()方法加載剩下的appointments實例。如代碼清單5-32所示。

代碼清單5-32.演示加載部分關聯實體集合

 //演示先加載部分實體集合,然后再加載剩下的
            using (var context = new EFRecipesEntities())
            {
                // 禁用延遲加載,因為我們要顯式加載子實體
                context.Configuration.LazyLoadingEnabled = false;

                //加載第一個doctor然后只附加一個appointment
                var doctorJoan = context.Doctors.First(o => o.Name == "Joan Meyers");
                context.Entry(doctorJoan).Collection(x => x.Appointments).Query().Take(1).Load();

                //注意,這里IsLoaded返回False,因為所有的實體還沒有被加載到上下文
                var appointmentsLoaded = context.Entry(doctorJoan).Collection(x => x.Appointments).IsLoaded;

                Console.WriteLine("Dr. {0} has {1} appointments loaded.",
                                  doctorJoan.Name,
                                  doctorJoan.Appointments.Count());

                //當我需要加載剩下的appointments,只需要簡單的調用Load()來加載它們
                context.Entry(doctorJoan).Collection(x => x.Appointments).Load();
                Console.WriteLine("Dr. {0} has {1} appointments loaded.",
                                  doctorJoan.Name,
                                  doctorJoan.Appointments.Count());
            }

代碼清單5-31的輸出如下:

Dr. Joan Meyers has 1 appointments loaded.
Dr. Joan Meyers has 2 appointments loaded.

 

 

實體框架交流QQ群:  458326058,歡迎有興趣的朋友加入一起交流

謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

 


免責聲明!

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



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