性能問題幾乎是一切ORM框架的通病,對於EF來講,引發性能低的緣由主要在如下幾個方面。sql
在EF應用中,編寫的任何一個查詢表達式都須要通過分析,解析成SQL語句,而後調用底層的ADO.NET Providers去執行。直接執行SQL語句相比,性能上有必定的下降。 數據庫
EF採用映射機制將對象操做轉換爲SQL語句,SQL語句通常是基於標準模板生成的,不會進行特殊的優化,這與直接針對業務編寫的SQL語句去操做數據相比,效率通常會打折扣,特別是複雜的數據庫操做。 緩存
Linq查詢最終生成的SQL語句是什麼樣的?咱們可使用ToString()方法直接輸出T-SQL代碼。如示例10所示。 框架
示例10 ide
using (MySchoolContext db = new MySchoolContext()) 性能 { 優化 var result = db.Student.Where(n => n.StudentName.Contains("張")); ui Console.WriteLine(result); spa }3d |
運行結果如圖2-10所示。
圖2-10 Linq查詢生成的SQL語句
固然,EF自己對性能有一系列的優化措施,會使用這寫手段的前提是對EF的執行機制有足夠的瞭解。
在程序中實現數據的增、刪、改操做,EF會監控這些狀態的變化,在執行SaveChange()方法時,會根據對象狀態的變化執行相應的操做。如示例11所示。
示例11
using (MySchoolContext db = new MySchoolContext()) { Grade grade = new Grade() { GradeName = "Y3" }; //輸出當前對象狀態 Console.WriteLine(db.Entry(grade).State);
db.Grade.Add(grade); Console.WriteLine(db.Entry(grade).State);
db.SaveChanges(); Console.WriteLine(db.Entry(grade).State); } |
示例11中,經過Entry()方法獲取模型狀態,該方法是DbContext類的成員方法,定義以下:
public DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity)
返回類型 DbEntityEntry<T> 封裝了對象狀態相關的信息。經常使用成員如表3-2所示。
表3-2 DbEntityEntry 的主要成員
方法或屬性 |
說明 |
CurrentValues |
獲取由此對象表示的跟蹤實體的當前屬性值 |
OriginalValues |
獲取由此對象表示的跟蹤實體的原始屬性值 |
State |
獲取或設置實體的狀態 |
Reload() |
從數據庫從新加載對象的值 |
其中,State屬性是一個EntityState枚舉類型,其取值以下:
Detached:表示對象存在,但沒有被跟蹤
Unchanged:表示對象還沒有通過修改
Added:表示對象爲新對象,而且已添加到對象上下文
Deleted:對象已從對象上下文中刪除
Modified:表示對象的一個標量屬性已更改
經過設置實體的State屬性也能夠實現對數據的操做,如:
using (MySchoolContext db = new MySchoolContext()) { Grade grade = new Grade() { GradeName = "Y3" }; db.Entry(grade).State = EntityState.Added; db.SaveChanges( ); } |
除了須要增刪改的對象會受狀態管理機制管理外,經過EF查詢的數據也默認會進行狀態管理。能夠經過兩種方式指定查詢不進行狀態管理。
方式一:使用AsNoTracking()方法,如示例12所示。
示例12
using (MySchoolContext db = new MySchoolContext()) { var result = db.Student.AsNoTracking().FirstOrDefault(); Console.WriteLine(db.Entry(result).State); } |
方式二:設置Configuration.AutoDetectChangesEnabled 屬性爲false,如示例13。
示例13
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修改或刪除數據時,必須先查詢對象,而後再對其進行修改或刪除。然而現實開發中不少狀況都是經過主鍵刪除一條數據。咱們能夠經過實體的狀態特性來進行優化。
示例14
using (MySchool1Entities entities = new MySchool1Entities()) { //建立替身對象 var stu = new Student { StudentNo = "10001" }; //給實體附加到上下文中 entities.Student.Attach(stu); //刪除 entities.Student.Remove(stu); entities.SaveChanges(); } |
代碼中的Attach()方法能夠將EntityState.Unchangee狀態的對象附加到上下文中。
又稱爲懶加載,只有每次調用子實體(外鍵所在的實體)的時候,纔去查詢數據庫, 主表數據加載的時候,不去查詢外鍵所在的從表。
實現延遲加載須要知足兩個條件:
也能夠關閉延遲加載,方法是:
db.Configuration.LazyLoadingEnabled = false;
關閉延遲加載後,查詢主表數據時候,主表的中從表實體爲null。
示例15
using (dbContext1 db = new dbContext1()) { Console.WriteLine("---------------- 01-延遲加載 ---------------"); //EF默認就是延遲加載,默認下面的語句就是true,因此下面語句註釋沒有任何影響 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); } } |
又名:當即加載、貪婪加載、預加載。查詢主表的時候經過Include()方法一次性將數據查詢了出來,在調用從表數據的時候,從緩存中讀取,無須查詢數據庫。
實現方式:
示例16
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); } } |
關閉了延遲加載,單純查詢了主表數據,這個時候須要從新查詢從表數據,就要用到顯式加載了。
使用步驟:
①:單個實體用:Reference
②:集合用:Collection
③:最後須要Load一下
示例17
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); } } |
何時使用延遲加載,何時又使用貪婪加載呢?
延遲加載只有在須要使用數據時加載,若是不須要使用實體的關聯數據,可使用延遲加載。使用貪婪加載適用於預先了解要使用什麼數據的狀況,利用這種方式一次性加載數據,能夠減小數據庫訪問次數。
從實際狀況來看,使用默認的延遲加載就能夠了,2次或3次的數據庫查詢是能夠接受的。而循環中屢次讀取數據庫,能夠考慮使用貪婪加載。
在使用EF時,有時會屢次使用一個查詢結果,如示例18所示。
示例18
using (MySchoolEntities db = new MySchoolEntities()) { //查詢所有學生 IQueryable<Student> stus = db.Student; Console.WriteLine("所有學生姓名:"); foreach (var stu in stus) { Console.WriteLine("學生姓名:{0}",stu.StudentName); } //查詢並輸出學生人數 Console.WriteLine("學生人數爲:{0}",db.Student.Count()); } |
示例18中會產生兩次查詢,但從需求來看,徹底沒有必要,由於第二次徹底能夠利用第一次查詢的結果。事實上,徹底可使用EF的緩存功能,直接利用緩存的結果,DbSet<T>的Local屬性正是用於提供緩存的數據。
示例18中,將"db.Student.Count()"替換爲"db.Student.Local.Count()",這樣就不會產生新的查詢了。
另外,DbSet<T>提供了 Find()方法,用於經過主鍵查找實體,其查詢速度比First()和FirstOrDefault()方法快的多,而且若是相應的實體已經被DbContext緩存,EF會在緩存中直接返回對應的實體,而不會執行數據庫訪問。
EF中的事務主要分爲三類,分別是SaveChanges、DbContextTransaction 和
TransactionScope。
在前面內容中,SaveChanges一次性將本地緩存中全部的狀態變化一次性提交到數據庫,這就是一個事務,要麼統一成功,要麼統一回滾。
示例19
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); } |
示例19中,若是三個操做中有任意一個出現錯誤,就會回滾,結果n爲0。
使用場景:EF調用SQL語句的時候使用該事務、 多個SaveChanges的狀況。
示例20
using (DbContext db = new CodeFirstModel()) { DbContextTransaction trans = null; try { //開啓事務 trans = db.Database.BeginTransaction(); //增長 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(); } } |
DbContextTransaction事務也適用於多個SaveChanges的狀況。
示例21
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(); } } } |
該種事務適用於多數據庫鏈接的狀況,在此不作介紹,請自行查閱相關資料。
EF雖然自己有不少優化機制,但和直接使用ADO.NET相比,仍是有必定的性能差距,所以EF在DbContext類的Database屬性裏提供了ExecuteSqlCommand()和SqlQuery()兩個方法,用來直接訪問數據庫。
方法的定義以下:
public int ExecuteSqlCommand(string sql, params object[] parameters)
用來執行增、刪、改操做,返回結果爲受影響行數。
方法的定義以下:
public DbRawSqlQuery<T> SqlQuery<T>(string sql, params object[] parameters);
用來執行查詢操做,返回結果是一個集合。
示例22
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]); } |
1 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 }
<<BaseDAL.cs>>