EF 原理及SQL 監控
EF 會自動把Where()、OrderBy()、Select()等這些編譯成“表達式樹(Expression Tree)”,然後會把表達式樹翻譯成SQL 語句去執行。(編譯原理,AST)因此不是“把數據都取到內存中,然後使用集合的方法進行數據過濾”,因此性能不會低。但是如果這個操作不能被翻譯成SQL語句,則或者報錯,或者被放到內存中操作,性能就會非常低。
-
怎麼查看真正執行的SQL是什麼樣呢?
DbContext有一個Database屬性,其中的Log屬性,是Action委託類型,也就是可以指向一個void A(string s)方法,其中的參數就是執行的SQL語句,每次EF執行SQL語句的時候都會執行Log。因此就可以知道執行了什麼SQL。
EF的查詢是“延遲執行”的,只有遍歷結果集的時候才執行select 查詢,ToList()內部也是遍歷結果集形成List。
查看Update操作,會發現只更新了修改的字段。
-
觀察一下前面學學習時候執行的SQL是什麼樣的。Skip().Take()被翻譯成了?Count()被翻譯成了?
var result = ctx.Persons.Where(p => p.Name.StartsWith("inlett"));//看看翻譯成了什麼? var result = ctx.Persons.Where(p => p.Name.Contains("com")); var result = ctx.Persons.Where(p => p.Name.Length>5); var result = ctx.Persons.Where(p => p.CreateDateTime>DateTime.Now); long[] ids = { 2,5,6};//不要寫成int[] var result = ctx.Persons.Where(p => ids.Contains(p.Id));
-
EF中還可以多次指定where來實現動態的複合檢索:
//必須寫成IQueryable<Person>,如果寫成IEnumerable 就會在內存中取後續數據 IQueryable<Person> items = ctx.Persons;//爲什麼把IQueryable<Person>換成var 會編譯出錯 items = items.Where(p=>p.Name=="inlett"); items = items.Where(p=>p.Id>5);
查看一下生成的SQL語句。
-
EF是跨數據庫的,如果遷移到MYSQL上,就會翻譯成MYSQL的語法。要配置對應數據庫的Entity Framework Provider。
-
細節:
每次開始執行的__MigrationHistory等這些SQL語句是什麼?是DBMigration用的,也就是由EF幫我們建數據庫,現在我們用不到,用下面的代碼禁用:
Database.SetInitializer(null);
XXXDbContext就是項目DbContext的類名。一般建議放到XXXDbContext構造函數中。注意這裏的Database
是System.Data.Entity下的類,不是DbContext的Database屬性。如果寫到DbContext中,最好用上全名,防止出錯。
執行原始SQL
不要“手裏有錘子,到處都是釘子”在一些特殊場合,需要執行原生SQL。
執行非查詢語句,調用DbContext的Database屬性的ExecuteSqlCommand方法,可以通過佔位符的方式傳遞參數:
ctx.Database.ExecuteSqlCommand("update T_Persons set Name={0},CreateDateTime=GetDate()","YLT.com");
佔位符的方式不是字符串拼接,經過觀察生成的SQL語句,發現仍然是參數化查詢,因此不會有SQL注入漏洞。
執行查詢:
var q1 = ctx.Database.SqlQuery<Item1>("select Name,Count(*) Count from T_Persons where Id>{0} and CreateDateTime<={1} group by Name",2, DateTime.Now); //返回值是DbRawSqlQuery<T>類型,也是實現IEnumerable 接口
foreach(var item in q1)
{
Console.WriteLine(item.Name+":"+item.Count);
}
class Item1
{
public string Name { get; set; }
public int Count { get; set; }
}
類似於ExecuteScalar的操作比較麻煩:
int c = ctx.Database.SqlQuery<int>("select count(*) from T_Persons").SingleOrDefault();
不是所有lambda 寫法都能被支持
下面想把Id轉換爲字符串比較一下是否爲"3"(別管爲什麼):
var result = ctx.Persons.Where(p => Convert.ToString(p.Id)=="3");
運行會報錯(也許高版本支持了就不報錯了),這是一個語法、邏輯上合法的寫法,但是EF目前無法把他解析爲一個SQL語句。
出現“System.NotSupportedException”異常一般就說明你的寫法無法翻譯成SQL語句
想獲取創建日期早於當前時間一小時以上的數據:
var result = ctx.Persons.Where(p => (DateTime.Now - p.CreateDateTime).TotalHours>1);
同樣也可能會報錯。
怎麼解決?
嘗試其他替代方案(沒有依據,只能亂試):
var result = ctx.Persons.Where(p => p.Id==3);
EF中提供了一個SQLServer專用的類SqlFunctions,對於EF不支持的函數提供了支持,比如:
var result = ctx.Persons.Where(p =>SqlFunctions.DateDiff("hour",p.CreateDateTime,DateTime.Now)>1);
EF對象的狀態
簡介
爲什麼查詢出來的對象Remove()、再SaveChanges()就會把數據刪除。而自己new一個Person()對象,然後Remove()不行?爲什麼查詢出來的對象修改屬性值後、再SaveChanges()就會把數據庫中的數據修改。
因爲EF會跟蹤對象狀態的改變。
EF中中對象有五個狀態:Detached(遊離態,脫離態)、Unchanged(未改變)、Added(新增)、Deleted(刪除)、Modified(被修改)。
Add()、Remove()修改對象的狀態。所有狀態之間幾乎都可以通過:Entry§.State=xxx的方式進行強制狀態轉換。
通過代碼來演示一下。這個狀態轉換圖沒必要記住,瞭解即可。
狀態改變都是依賴於Id的(Added除外)
應用(*)
當SavaChanged()方法執行期間,會查看當前對象的EntityState的值,決定是去新增(Added)、修改Modified)、刪除(Deleted)或者什麼也不做(UnChanged)。下面的做法不推薦,在舊版本中一些寫法不被支持,到新版EF中可能也會不支持。
-
不先查詢再修改再保存,而是直接更新部分字段的方法
var p = new Person(); p.Id = 2; ctx.Entry(p).State = System.Data.Entity.EntityState.Unchanged; p.Name = "adfad"; ctx.SaveChanges();
也可以:
var p = new Person(); p.Id = 5; p.Name = "yltedu"; ctx.Persons.Attach(p);//等價於ctx.Entry(p).State = System.Data.Entity.EntityState.Unchanged; ctx.Entry(p).Property(a => a.Name).IsModified = true; ctx.SaveChanges();
-
不先查詢再Remove再保存,而是直接根據Id刪除的方法:
var p = new Person(); p.Id = 2; ctx.Entry(p).State = System.Data.Entity.EntityState.Deleted; ctx.SaveChanges();
注意下面的做法並不會刪除所有Name=“ylt.com” 的,因爲更新、刪除等都是根據Id進行的:
var p = new Person(); p.Name = "yltedu.com"; ctx.Entry(p).State = System.Data.Entity.EntityState.Deleted; ctx.SaveChanges();
上面其實是在:
delete * from t_persons where Id=0
EF優化的一個技巧
如果查詢出來的對象只是供顯示使用,不會修改、刪除後保存,那麼可以使用AsNoTracking()來使得查詢出來的對象是Detached狀態,這樣對對象的修改也還是Detached狀態,EF不再跟蹤這個對象狀態的改變,能夠提升性能。
var p1 = ctx.Persons.Where(p => p.Name == "rupeng.com").FirstOrDefault();
Console.WriteLine(ctx.Entry(p1).State);
改成:
var p1 = ctx.Persons.AsNoTracking().Where(p => p.Name == "rupeng.com").FirstOrDefault();
Console.WriteLine(ctx.Entry(p1).State);
因爲AsNoTracking()是DbQuery類(DbSet的父類)的方法,所以要先在DbSet後調用AsNoTracking()。