APS.NET MVC + EF (02)---深刻理解ADO.NET Entity Framework

2.7 深刻理解Entity Framework

性能問題幾乎是一切ORM框架的通病,對於EF來講,引發性能低的緣由主要在如下幾個方面。sql

  • 複雜的對象管理機制
    爲了在
    .NET中更好地管理模型對象,EF提供了一套內部機制管理和跟蹤對象的狀態,保持對象的一致性,帶來方便的同時,下降了性能。
  • 高度封裝的執行機制

    在EF應用中,編寫的任何一個查詢表達式都須要通過分析,解析成SQL語句,而後調用底層的ADO.NET Providers去執行。直接執行SQL語句相比,性能上有必定的下降。 數據庫

  • 低效的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的執行機制有足夠的瞭解。

   

2.7.1 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狀態的對象附加到上下文中。

   

2.7.2 延遲加載和貪婪加載

  1. 延遲加載

    又稱爲懶加載,只有每次調用子實體(外鍵所在的實體)的時候,纔去查詢數據庫, 主表數據加載的時候,不去查詢外鍵所在的從表。

    實現延遲加載須要知足兩個條件:

  • poco類是public且不能爲sealed。
  • 導航屬性須要標記爲Virtual。

也能夠關閉延遲加載,方法是:

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

}

}

   

  1. 貪婪加載

    又名:當即加載、貪婪加載、預加載。查詢主表的時候經過Include()方法一次性將數據查詢了出來,在調用從表數據的時候,從緩存中讀取,無須查詢數據庫。

    實現方式:

  • 先關閉延遲加載:db.Configuration.LazyLoadingEnabled = false;
  • 查詢主表的同時經過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);

}

}

   

  1. 顯示加載

    關閉了延遲加載,單純查詢了主表數據,這個時候須要從新查詢從表數據,就要用到顯式加載了。

    使用步驟:

    ①:單個實體用: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);

}

}

   

  1. 小結

    何時使用延遲加載,何時又使用貪婪加載呢?

    延遲加載只有在須要使用數據時加載,若是不須要使用實體的關聯數據,可使用延遲加載。使用貪婪加載適用於預先了解要使用什麼數據的狀況,利用這種方式一次性加載數據,能夠減小數據庫訪問次數。

    從實際狀況來看,使用默認的延遲加載就能夠了,2次或3次的數據庫查詢是能夠接受的。而循環中屢次讀取數據庫,能夠考慮使用貪婪加載。

2.7.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會在緩存中直接返回對應的實體,而不會執行數據庫訪問。

   

2.7.4 EF中的事務

EF中的事務主要分爲三類,分別是SaveChanges、DbContextTransaction 和

TransactionScope。

  1. SaveChanges事務

    在前面內容中,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。

   

  1. DbContextTransaction 事務

    使用場景: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();

}

}

}

   

  1. TransactionScope事務

    該種事務適用於多數據庫鏈接的狀況,在此不作介紹,請自行查閱相關資料。

   

2.7.5 從實體框架回歸SQL

EF雖然自己有不少優化機制,但和直接使用ADO.NET相比,仍是有必定的性能差距,所以EF在DbContext類的Database屬性裏提供了ExecuteSqlCommand()和SqlQuery()兩個方法,用來直接訪問數據庫。

  1. ExecuteSqlCommand()

    方法的定義以下:

    public int ExecuteSqlCommand(string sql, params object[] parameters)

    用來執行增、刪、改操做,返回結果爲受影響行數。

  2. SqlQuery()

    方法的定義以下:

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

}

   

2.8 封裝EFDAL

   

 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     }
View Code

 

 

<<BaseDAL.cs>>

相關文章
相關標籤/搜索