1. 程式人生 > 實用技巧 >Entity Framework 實體資料模型——資料處理

Entity Framework 實體資料模型——資料處理

一、關於 DbContext

  在 Entity Framework 環境下,程式通過實體資料模型與底層資料庫進行溝通。

  資料模型是一個繼承自 DbContext 的自定義類檔案,也就是context。

  無論是使用 “EF 設計器” 還是 “Code First”,都必須經由 context 進行資料庫的互動操作。

  下面通過例項來介紹下DbContext 物件。

  通過已有的資料庫,選擇 “來自資料庫的Code First” 模式來建立實體資料庫模型例項。

   

  通過模型類 StudentInfoDBModel可以看出,它繼承自 DbContext 並在 EF 中負責幾項關鍵任務。

  對 DbSet<T> 類物件的管理,每一個DbSet<TEntity> 物件都是封裝特定資料的實體集(Entity Set)物件,

  實體集對映到底層資料庫的特定資料表。

  在 EF 環境中對任何資料庫執行的存取操作,都會生成對應到連線資料庫的SQL語句,

  而 EF 負責將語句放到資料庫中執行並返回執行結果。

  

  在 Program.cs 類的 Main 方法中新增如下程式碼:

  using (var context = new StudentInfoDBModel())  // DbContext 物件會佔用資源,通常我們會使用 using 語句來建立物件例項
{ IEnumerable<Student> stus = from stu in context.Student select stu; string sql = stus.ToString(); Console.WriteLine($"{sql}"); } Console.ReadLine();

  執行程式:

  

  可以看到EF生成了對應的SQL語句。

  當我們想要讀取到對應的資料時,這段SQL將被傳遞給資料庫去進行執行,並返回相應的執行結果。

  然後通過 Context 物件將返回的資料轉換成對應的可列舉的IEnumerable<Student> 集合物件。

 static void Main(string[] args)
 {
     using (var context = new StudentInfoDBModel())  
     {
         IEnumerable<Student> stus = from stu in context.Student select stu;
         string sql = stus.ToString();
         Console.WriteLine($"{sql}");
         foreach (var stu in stus)
         {
             Console.WriteLine(stu.Name);
         }
     }
     Console.ReadLine();
 }

  EF 除了負責產生SQL語句外,還會管理SQL的傳送與返回的資料封裝,因此我們只需處理LINQ

  與資料物件即可。因此避免了因編寫SQL而導致的錯誤和安全問題。

  1、 連線與查詢

    EF 的資料庫連線由 DbContext 自行維護,當一個查詢開始執行時,連線便會適時的開啟,直到查詢結束再自動關閉連線。

    連線是相對耗費資源的操作,因此我們需要了解下 EF 是在何時進行SQL語句傳遞的,以避免不必要的效能耗費。那麼,

    DbContext 會在何時去執行查詢操作呢?

    ① 進行遍歷物件時

      比如上面的 LINQ 語句,當程式執行到這段LINQ時,查詢操作其實並不會馬上執行,而是在執行foreach 語句時,

      查詢操作才會執行。

    ② 型別轉換時

     LINQ 語句返回的通常是 IEnumerable 或 IQueryable 物件,有時我們會將其轉換成 List 類的物件,在進行型別裝換

     的過程中(比如呼叫 ToList()、ToArray()、ToDictionary()等方法時),也會執行查詢操作。

      

    ③ 呼叫任何針對LINQ結果執行的方法時,如呼叫 Count()、First()等方法時。

     

    ④ 重新載入實體資料時

      比如在進行更新操作以後,想要獲取最新的資料內容時,可以通過過載來達到目的,這個時候查詢就會再被執行一次。

  2、 管理更新操作

    EF 中的變動更新操作

     using (var context = new StudentInfoDBModel())
     {
         Student stu = context.Student.First();   // 獲取 Student 表中的第一條記錄
         Console.WriteLine($"姓名:{stu.Name} \t 年齡:{stu.Age}");
 
         stu.Age = 18;   // 更新年齡
         context.SaveChanges();  // SaveChanges()方法儲存變更到資料控中
         Console.WriteLine($"姓名:{stu.Name} \t 年齡:{stu.Age}");
 
         //過載
         context.Entry(stu).Reload();
         stu = context.Student.First();
         Console.WriteLine($"姓名:{stu.Name} \t 年齡:{stu.Age}");
     }
     Console.ReadLine();

    可以看到,在更新年齡之後,程式呼叫了context.SaveChanges(); 方法將更新後的資料儲存到了資料庫中。

    這麼一來資料物件就發生了改變,如果想要實時獲取更新後的資料內容,那麼就需要進行過載 DbContext 物件。

    以上示例中呼叫的 First()方法 和 Reload() 過載方法都會導致連線的建立,並且執行底層資料庫的查詢操作。

  3、 DbContext 物件的生命週期

    DbContext 物件會佔用資源,通常我們會使用 using 語句來建立物件例項,當 using 區塊結束時,物件的生命

    週期也就相應的結束了,並且會釋放其佔用的資源。

    如果直接建立 DbContext 物件,就必須呼叫 Dispose 方法;如果不呼叫該方法的話,就只能等待系統的資源

    回收機制去進行資源釋放了。

  4、 管理與操作資料庫(Database屬性)

    DbContext 有一個 Database 屬性,它會返回一個 System.Data.Entity.Database 物件,為 DbContext 提供連線資料庫的支援。

    

    這行程式碼獲取了 Database 物件,我們可以通過 db 變數來執行有關資料庫的操作。

    Database 中有一組 log 屬性支援獲取 DbContext 生成的 SQL 記錄。我們可以通過該屬性來追蹤程式執行過程中所使用的SQL。

     using (var context = new StudentInfoDBModel()) 
     {
         context.Database.Log = Console.WriteLine;  // 通過該配置,可以跟蹤程式執行中所用到的 SQL 語句
         IEnumerable<Student> stus = from stu in context.Student select stu;
         foreach (var item in stus)
         {
             Console.WriteLine(item.Name);
         }
     }
     Console.ReadLine();

    

    我們還可以通過context.Database.Connection 物件來獲取連線的相關資訊。

      using (var context = new StudentInfoDBModel()) 
      {
          DbConnection dbCon = context.Database.Connection;   // 獲取資料庫的連線物件
          string conn = dbCon.ConnectionString;               // 獲取資料庫的連線字串
          string dbName = dbCon.Database;                     // 獲取資料庫名稱
          string serverName = dbCon.DataSource;               // 獲取要連線到資料庫伺服器的名稱。
          System.Data.ConnectionState state = dbCon.State;    // 獲取連線狀態

          Console.WriteLine($"連線字串:{conn} \n資料庫:{dbName} \n伺服器:{serverName} \n狀態:{state}");
      }
      Console.ReadLine();

  5、ObjectContext

    在 EF 4.1 之前,支援資料庫操作功能實現的類必須繼承ObjectContext,直到 EF 4.1 之後更易於使用的 DbConext

    才被公佈了出來,成為了預設的繼承類,ObjectContext 可視為輕量級版的 DbContext,而 DbContext 依然實現了

    IObjectContextAdapter.ObjectContext 屬性,以返回底層的ObjectContext 物件。

     System.Data.Entity.Core.Objects.ObjectContext objDbCont = (context as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext;

    DbContext 簡化了 ObjectContext 的用法:

     using (var context = new StudentInfoDBModel()) 
     {
         // ObjectContext
         ObjectContext objDbCont = (context as IObjectContextAdapter).ObjectContext;
         ObjectSet<Student> objStu = objDbCont.CreateObjectSet<Student>();
         var oq = (ObjectQuery)objStu.Where(s => s.Age > 16);
         Console.WriteLine(oq.ToTraceString());
         foreach (Student item in oq)
         {
             Console.WriteLine(item.Name);
         }

         // DbContext
         var stu = context.Student.Where(s => s.Age > 16);
         Console.WriteLine(stu.ToString());
         foreach (var st in stu)
         {
             Console.WriteLine(st.Name);
         }
     }
     Console.ReadLine();

    

    不同的實現方式,預期的效果相同,相比之下 DbContext 更為簡潔。

二、關於 DbSet

  1、DbSet

    DbSet 也是 EF 重要的類之一,表示實體模型中的特定實體物件集合。

    當 DbContext 將查詢送入資料庫並獲取返回的資料時,這些資料將被轉換為對應的實體類物件,然後儲存在

    DbSet 中,並通過 DbContext 物件屬性公開,以供程式提取。

    DbSet 實現了 IEnumerable 介面,所以支援 LINQ 語法和方法。程式通過 LINQ 生成原始的 SQL 語句,並

    由 DbContext 傳送至資料庫執行。我們可以通過 ToString() 方法來獲取對應的 SQL 語句。

     using (var context = new StudentInfoDBModel())
     {
         string sql = context.Student.ToString();
         Console.WriteLine(sql);
     }
     Console.ReadLine();

    上面的 context.Student 會返回 DbSet<Student> 物件,其中封裝了來自 Student 表的內容,因此我們可以通過

    ToString() 的方法來獲取到對應的 SQL 語句。

    

  2、查詢資料(Find() 方法)

    Find() 方法必須指定所有主鍵值才能正確地執行。

     using (var context = new StudentInfoDBModel())
     {
         Student stu = context.Student.Find(5);
         if (stu != null)
             Console.WriteLine(stu.Name);
         else
             Console.WriteLine("不存在");
     }
     Console.ReadLine();

    Find(5) 表示查詢 ID 欄位值為 5 的一條資料。

    Find() 方法必須傳入實體類中定義的所有主鍵屬性才能正確的進行查詢。

    也就是說 Find() 方法是基於主鍵進行查詢的。如果複合式索引主鍵,就需要依次傳入所有主鍵欄位。否則:

    

    注意: Find() 方法查詢資料的方式在效能方面要優於 LINQ 的方式。

  3、執行原始的 SQL 語句

    如果想要直接執行已有的 SQL 語句,可以通過呼叫 SqlQuery() 方法:

      using (var context = new StudentInfoDBModel())
      {
          SqlParameter sqlPara = new SqlParameter("Age", 16);
          var stus = context.Student.SqlQuery("select * from Student where Age > @Age", sqlPara);
          foreach (var stu in stus) 
          {
              Console.WriteLine(stu.Name);
          }
      }
      Console.ReadLine();

  4、資料變動與更新

    DbSet 支援物件的變動和更新。無論是修改或刪除集合中的資料,還是往集合中新增資料,這些針對 DbSet 執行的操作,

    都會隨著呼叫 SaveChange 方法同步更新到 DBContext 所連線的資料庫中。

    新增物件需呼叫 Add() 方法:

     using (var context = new StudentInfoDBCModel())
     {
         Student sun = new Student
         {
             Name = "孫悟空",
             Age = 12,
             Sex = false,
             Hobby = "七十二變",
             Birthday = DateTime.Now
         };

         DbSet<Student> stus = context.Student;
         stus.Add(sun);
         int result = context.SaveChanges();
         Console.WriteLine(result);
 
         foreach (var stu in stus)
         {
             Console.WriteLine(stu.Name);
         }
     }
     Console.ReadLine();

    新增的資料量過多的話,可以通過呼叫 AddRange() 方法來實現:

     using (var context = new StudentInfoDBCModel())
     {
         List<Student> list = new List<Student>(){
             new Student
             {
                 Name = "孫悟空",
                 Age = 12,
                 Sex = false,
                 Hobby = "七十二變",
                 Birthday = DateTime.Now
             },
             new Student
             {
                 Name = "豬八戒",
                 Age = 12,
                 Sex = false,
                 Hobby = "三十六變",
                 Birthday = DateTime.Now
             }
         };
         DbSet<Student> stus = context.Student;
         stus.AddRange(list);
         int result = context.SaveChanges();
         Console.WriteLine(result);

         foreach (var stu in stus)
         {
             Console.WriteLine(stu.Name);
         }
     }
     Console.ReadLine();

    修改和新增都有了,還少個刪除。刪除呼叫的是 Remove() 或 RemoveRange() 方法:

    刪除的操作是針對特定的資料物件,因此在刪除之前必須先獲取物件。

     using (var context = new StudentInfoDBCModel())
     {
         // Remove
         Student stud = context.Student.Find(1);
         if(stud != null)
         {
             context.Student.Remove(stud);
             context.SaveChanges();
         }
         else
         {
             Console.WriteLine("不存在!");
         }
 
         // RemoveRange
         IQueryable<Student> stus = context.Student.Where(s => s.Age > 6);
         context.Student.RemoveRange(stus);
         context.SaveChanges();
     }
     Console.ReadLine();