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();