深入理解ADO.NET Entity Framework(02)
1.ORM
物件對映關係對資料庫進行操作,解決面向物件與關係資料庫不匹配的現象。
1.1 ORM效能問題
- 複雜的物件管理機制:實時跟蹤,保持物件的一致性的同時降低了效能。
- 高度封裝執行機制:編寫的任何表示式都要解析成SQL語句。
- 低效的SQL語句:對映機制轉將物件操作換為SQL語句,效率低。
1.2 EF的狀態管理
在程式中實現資料的增、刪、改操作,EF會監控這些狀態的變化,在執行SaveChange()方法時,會根據物件狀態的變化執行相應的操作。
using (MySchoolContext db = new MySchoolContext()) { Grade grade = new Grade() { GradeName = "Y3" }; //輸出當前物件狀態 Console.WriteLine(db.Entry(grade).State); //通過Entry()方法獲取模型狀態
db.Grade.Add(grade); Console.WriteLine(db.Entry(grade).State);
db.SaveChanges(); Console.WriteLine(db.Entry(grade).State); } |
方法或屬性 |
說明 |
CurrentValues |
獲取由此物件表示的跟蹤實體的當前屬性值 |
OriginalValues |
獲取由此物件表示的跟蹤實體的原始屬性值 |
State |
獲取或設定實體的狀態 |
Reload() |
從資料庫重新載入物件的值 |
其中,State屬性是一個EntityState列舉型別,其取值如下:
Detached | 表示物件存在,但沒有被跟蹤 |
Unchanged | 表示物件尚未經過修改 |
Added | 表示物件為新物件,並且已新增到物件上下文 |
Deleted | 物件已從物件上下文中刪除 |
Modified | 表示物件的一個標量屬性已更改 |
1.3查詢不進行跟蹤
- AsNoTracking()方法
using (MySchoolContext db = new MySchoolContext()) { var result = db.Student.AsNoTracking().FirstOrDefault(); Console.WriteLine(db.Entry(result).State); } |
- 設定Configuration.AutoDetectChangesEnabled 屬性為false
using (MySchoolContext db = new MySchoolContext()) { //禁用自動跟蹤變化 db.Configuration.AutoDetectChangesEnabled = false; for (int i = 0; i < 5000; i++) { var stu = new Student() { StudentName = "alex", GradeId = 1, Age = 20 }; db.Student.Add(stu); } db.SaveChanges(); } |
在使用EF修改或刪除資料時,必須先查詢物件,然後再對其進行修改或刪除。然而現實開發中很多情況都是通過主鍵刪除一條資料。我們可以通過實體的狀態特性來進行優化。
using (MySchool1Entities entities = new MySchool1Entities()) { //建立替身物件 var stu = new Student { StudentNo = "10001" }; //給實體附加到上下文中 entities.Student.Attach(stu); //刪除 entities.Student.Remove(stu); entities.SaveChanges(); } |
程式碼中的Attach()方法可以將EntityState.Unchangee狀態的物件附加到上下文中。
2.載入
- 延遲載入
每次呼叫時再去查詢,兩個條件:①Poco類是Public且不能為Sealed。②導航屬性需要標記為Vritual。
- 貪懶載入
一次性將資料讀取出來,從快取中讀取,不用在查詢資料庫,兩個條件:①先關閉延遲載入。②查詢主表的同時通過Include把從表資料也查詢出來。
- 顯示載入
--步驟:①單個實體用:Reference ②集合用:Collection ③最後需要Load一下
注意:預設用延遲,多次讀資料庫用貪婪。
//延遲載入 using (dbContext1 db = new dbContext1()) { Console.WriteLine("---------------- 01-延遲載入 ---------------"); //EF預設就是延遲載入,預設下面的語句就是true,關閉則false db.Configuration.LazyLoadingEnabled = true;
var list = db.Student.ToList(); //此處載入的資料,沒有對從表進行任何查詢操作 foreach (var stu in list) { Console.WriteLine("學生編號:{0},學生姓名:{1}", stu.studentId, stu.studentName); //下面呼叫導航屬性(一對一的關係) 每次呼叫時,都要去查詢資料庫 var stuAddress = stu.StudentAddress; Console.WriteLine("地址編號:{0},地址名稱:{1}", stuAddress.studentAddressId, stu.studentName); } } |
//貪懶載入 using (dbContext1 db = new dbContext1()) { Console.WriteLine("------------------- 03-立即載入 ------------------");
//1.關閉延遲載入 db.Configuration.LazyLoadingEnabled = false;
//2. 獲取主表資料的同時,通過Include將從表中的資料也全部加載出來 var list = db.Student.Include("StudentAddress").ToList(); foreach (var stu in list) { Console.WriteLine("學生編號:{0},學生姓名:{1}", stu.studentId, stu.studentName); //這裡獲取從表中的資料,均是從快取中獲取,無需查詢資料庫 var stuAddress = stu.StudentAddress; Console.WriteLine("地址編號:{0},地址名稱:{1}", stuAddress.studentAddressId, stu.studentName); } } |
//顯示載入 using (dbContext1 db = new dbContext1()) { Console.WriteLine("----------------- 04-顯式載入 ------------------"); //1.關閉延遲載入 db.Configuration.LazyLoadingEnabled = false;
//2.此處載入的資料,不含從表中的資料 var list = db.Student.ToList(); foreach (var stu in list) { Console.WriteLine("學生編號:{0},學生姓名:{1}", stu.studentId, stu.studentName); //3.下面的這句話,可以開啟重新查詢一次資料庫 //3.1 單個屬性的情況用Refercence db.Entry<Student>(stu).Reference(c => c.StudentAddress).Load(); //3.2 集合的情況用Collection //db.Entry<Student>(stu).Collection(c => c.StudentAddress).Load();
//下面呼叫導航屬性(一對一的關係) 每次呼叫時,都要去查詢資料庫 var stuAddress = stu.StudentAddress; Console.WriteLine("地址編號:{0},地址名稱:{1}", stuAddress.studentAddressId, stu.studentName); } } |
3.快取
- EF的dbset<T>的Local屬性提供快取
- DbSet<T>提供了 Find()方法,用於通過主鍵查詢實體速度比First方法快的多,並且如果相應的實體已經被DbContext快取,EF會在快取中直接返回對應的實體,不執行資料庫訪問。
4.事務
- SaveChanges
- DbContextTransaction
- TransactionScope
using (DbContext db = new CodeFirstModel()) { //增加 TestInfor t1 = new TestInfor() { id = Guid.NewGuid().ToString("N"), txt1 = "txt1", txt2 = "txt2" }; db.Set<TestInfor>().Add(t1); //刪除 TestInfor t2 = db.Set<TestInfor>().Where(u => u.id == "1").FirstOrDefault(); if (t2 != null) { db.Set<TestInfor>().Remove(t2); } //修改 TestInfor t3 = db.Set<TestInfor>().Where(u => u.id == "3").FirstOrDefault(); t3.txt2 = "我是李馬茹23";
//SaveChanges事務提交 int n = db.SaveChanges(); //一次性統一或回滾 Console.WriteLine("資料作用條數:" + n); } |
using (DbContext db = new CodeFirstModel()) { DbContextTransaction trans = null; try { //開啟事務 trans = db.Database.BeginTransaction(); //多個SaveChanges //增加 string sql1 = @"insert into TestInfor values(@id,@txt1,@txt2)"; SqlParameter[] pars1 ={ new SqlParameter("@id",Guid.NewGuid().ToString("N")), new SqlParameter("@txt1","txt11"), new SqlParameter("@txt2","txt22") }; db.Database.ExecuteSqlCommand(sql1, pars1); //刪除 string sql2 = @"delete from TestInfor where id=@id"; SqlParameter[] pars2 ={ new SqlParameter("@id","22") }; db.Database.ExecuteSqlCommand(sql2, pars2); //修改 string sql3 = @"update TestInfor set txt1=@txt1 where id=@id"; SqlParameter[] pars3 ={ new SqlParameter("@id","3"), new SqlParameter("@txt1","二狗子") }; db.Database.ExecuteSqlCommand(sql3, pars3);
//提交事務 trans.Commit(); Console.WriteLine("事務成功了"); } catch (Exception ex) { Console.WriteLine(ex.Message); trans.Rollback(); //回滾
} finally { //也可以把該事務寫到using塊中,讓其自己託管,就不需要手動釋放了 trans.Dispose(); } } |
using (DbContext db = new CodeFirstModel()) { //自動脫管,不需要手動釋放多資料庫連線 using (DbContextTransaction trans = db.Database.BeginTransaction()) { try { TestInfor t1 = new TestInfor() { id = Guid.NewGuid().ToString("N"), txt1 = "111111111", txt2 = "222222222222" }; db.Entry(t1).State = EntityState.Added; db.SaveChanges();
TestInfor t2 = new TestInfor() { id = Guid.NewGuid().ToString("N") + "123", txt1 = "111111111", txt2 = "222222222222" }; db.Entry(t2).State = EntityState.Added; db.SaveChanges();
trans.Commit(); } catch (Exception) { trans.Rollback(); } } } |
5.從實體框架回歸SQL
EF在DbContext類的Database屬性裡提供了ExecuteSqlCommand()和SqlQuery()兩個方法,用來直接訪問資料庫。
- ExecuteSqlCommand()
- SqlQuery()
using (MySchool1Entities db = new MySchool1Entities()) { //執行update語句 string sql = "update grade set gradeName=@gradeName where gradeId=@gradeId"; SqlParameter[] ps = { new SqlParameter("@gradeName","第二學年"), new SqlParameter("@gradeId",3) }; int result=db.Database.ExecuteSqlCommand(sql, ps);//返回影響行數 if (result>0) { Console.WriteLine("資料更新完成!"); } //執行查詢語句 sql = "select * from from student where studentNo=@stuNo"; ps = new SqlParameter[] { new SqlParameter("@stuNo", "S1001234") }; var stu = db.Database.SqlQuery<Student>(sql, ps);//返回集合 Console.WriteLine(stu.ToList()[0]); } |
封裝EF的DAL層
public class BaseDAL<T> where T:class 2 { 3 private DbContext db 4 { 5 get 6 { 7 DbContext dbContext = CallContext.GetData("dbContext") as DbContext; 8 if (dbContext == null) 9 { 10 dbContext = new MySchoolContext(); 11 CallContext.SetData("dbContext", dbContext); 12 } 13 return dbContext; 14 } 15 } 16 17 /// <summary> 18 /// 執行增加,刪除,修改操作(或呼叫儲存過程) 19 /// </summary> 20 /// <param name="sql"></param> 21 /// <param name="pars"></param> 22 /// <returns></returns> 23 public int ExecuteSql(string sql, params SqlParameter[] pars) 24 { 25 return db.Database.ExecuteSqlCommand(sql, pars); 26 } 27 28 /// <summary> 29 /// 執行查詢操作 30 /// </summary> 31 /// <typeparam name="T"></typeparam> 32 /// <param name="sql"></param> 33 /// <param name="pars"></param> 34 /// <returns></returns> 35 public List<T> ExecuteQuery(string sql, params SqlParameter[] pars) 36 { 37 return db.Database.SqlQuery<T>(sql, pars).ToList(); 38 } 39 40 /// <summary> 41 /// 新增 42 /// </summary> 43 /// <param name="model"></param> 44 /// <returns></returns> 45 public int Add(T model) 46 { 47 db.Set<T>().Add(model); 48 return db.SaveChanges(); 49 } 50 51 /// <summary> 52 /// 刪除(適用於先查詢後刪除的單個實體) 53 /// </summary> 54 /// <param name="model">需要刪除的實體</param> 55 /// <returns></returns> 56 public int Del(T model) 57 { 58 db.Set<T>().Attach(model); 59 db.Set<T>().Remove(model); 60 return db.SaveChanges(); 61 } 62 63 /// <summary> 64 /// 根據條件刪除(支援批量刪除) 65 /// </summary> 66 /// <param name="delWhere">傳入Lambda表示式(生成表示式目錄樹)</param> 67 /// <returns></returns> 68 public int DelBy(Expression<Func<T, bool>> delWhere) 69 { 70 var listDels = db.Set<T>().Where(delWhere); 71 foreach(var model in listDels) 72 { 73 db.Set<T>().Attach(model); 74 db.Set<T>().Remove(model); 75 } 76 return db.SaveChanges(); 77 } 78 79 /// <summary> 80 /// 修改 81 /// </summary> 82 /// <param name="model">修改後的實體</param> 83 /// <returns></returns> 84 public int Modify(T model) 85 { 86 db.Entry(model).State = EntityState.Modified; 87 return db.SaveChanges(); 88 } 89 90 /// <summary> 91 /// 批量修改 92 /// </summary> 93 /// <param name="model">要修改實體中 修改後的屬性 </param> 94 /// <param name="whereLambda">查詢實體的條件</param> 95 /// <param name="proNames">lambda的形式表示要修改的實體屬性名</param> 96 /// <returns></returns> 97 public int ModifyBy(T model, Expression<Func<T, bool>> whereLambda, params string[] proNames) 98 { 99 List<T> listModifes = db.Set<T>().Where(whereLambda).ToList(); 100 Type t = typeof(T); 101 List<PropertyInfo> proInfos = t.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList(); 102 Dictionary<string, PropertyInfo> dicPros = new Dictionary<string, PropertyInfo>(); 103 proInfos.ForEach(p => 104 { 105 if (proNames.Contains(p.Name)) 106 { 107 dicPros.Add(p.Name, p); 108 } 109 }); 110 foreach (string proName in proNames) 111 { 112 if (dicPros.ContainsKey(proName)) 113 { 114 PropertyInfo proInfo = dicPros[proName]; 115 object newValue = proInfo.GetValue(model, null); 116 foreach (T m in listModifes) 117 { 118 proInfo.SetValue(m, newValue, null); 119 } 120 } 121 } 122 return db.SaveChanges(); 123 } 124 125 /// <summary> 126 /// 根據條件查詢 127 /// </summary> 128 /// <param name="whereLambda">查詢條件(lambda表示式的形式生成表示式目錄樹)</param> 129 /// <returns></returns> 130 public IQueryable<T> GetListBy(Expression<Func<T, bool>> whereLambda) 131 { 132 return db.Set<T>().Where(whereLambda); 133 } 134 /// <summary> 135 /// 根據條件排序和查詢 136 /// </summary> 137 /// <typeparam name="Tkey">排序欄位型別</typeparam> 138 /// <param name="whereLambda">查詢條件</param> 139 /// <param name="orderLambda">排序條件</param> 140 /// <param name="isAsc">升序or降序</param> 141 /// <returns></returns> 142 public IQueryable<T> GetListBy<Tkey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) 143 { 144 if (isAsc) 145 { 146 return db.Set<T>().Where(whereLambda).OrderBy(orderLambda); 147 } 148 else 149 { 150 return db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda); 151 } 152 } 153 /// <summary> 154 /// 分頁查詢 155 /// </summary> 156 /// <typeparam name="Tkey">排序欄位型別</typeparam> 157 /// <param name="pageIndex">頁碼</param> 158 /// <param name="pageSize">頁容量</param> 159 /// <param name="whereLambda">查詢條件</param> 160 /// <param name="orderLambda">排序條件</param> 161 /// <param name="isAsc">升序or降序</param> 162 /// <returns></returns> 163 public IQueryable<T> GetPageList<Tkey>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) 164 { 165 166 IQueryable<T> list = null; 167 if (isAsc) 168 { 169 list = db.Set<T>().Where(whereLambda).OrderBy(orderLambda) 170 .Skip((pageIndex - 1) * pageSize).Take(pageSize); 171 } 172 else 173 { 174 list = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda) 175 .Skip((pageIndex - 1) * pageSize).Take(pageSize); 176 } 177 return list; 178 } 179 /// <summary> 180 /// 分頁查詢輸出總行數 181 /// </summary> 182 /// <typeparam name="Tkey">排序欄位型別</typeparam> 183 /// <param name="pageIndex">頁碼</param> 184 /// <param name="pageSize">頁容量</param> 185 /// <param name="whereLambda">查詢條件</param> 186 /// <param name="orderLambda">排序條件</param> 187 /// <param name="isAsc">升序or降序</param> 188 /// <returns></returns> 189 public IQueryable<T> GetPageList<Tkey>(int pageIndex, int pageSize, out int rowCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true) 190 { 191 IQueryable<T> list = null; 192 rowCount = db.Set<T>().Where(whereLambda).Count(); 193 if (isAsc) 194 { 195 list = db.Set<T>().Where(whereLambda).OrderBy(orderLambda) 196 .Skip((pageIndex - 1) * pageSize).Take(pageSize); 197 } 198 else 199 { 200 list = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda) 201 .Skip((pageIndex - 1) * pageSize).Take(pageSize); 202 } 203 return list; 204 } 205 206 207 }View Code
Detached:表示物件存在,但沒有被跟蹤
Unchanged:表示物件尚未經過修改
Added:表示物件為新物件,並且已新增到物件上下文
Deleted:物件已從物件上下文中刪除
Modified:表示物件的一個標量屬性已更改