EF(四)-- EF原理及對象

EF 原理及SQL 監控

EF 會自動把Where()、OrderBy()、Select()等這些編譯成“表達式樹(Expression Tree)”,然後會把表達式樹翻譯成SQL 語句去執行。(編譯原理,AST)因此不是“把數據都取到內存中,然後使用集合的方法進行數據過濾”,因此性能不會低。但是如果這個操作不能被翻譯成SQL語句,則或者報錯,或者被放到內存中操作,性能就會非常低。

  1. 怎麼查看真正執行的SQL是什麼樣呢?

    DbContext有一個Database屬性,其中的Log屬性,是Action委託類型,也就是可以指向一個void A(string s)方法,其中的參數就是執行的SQL語句,每次EF執行SQL語句的時候都會執行Log。因此就可以知道執行了什麼SQL。

    EF的查詢是“延遲執行”的,只有遍歷結果集的時候才執行select 查詢,ToList()內部也是遍歷結果集形成List。

    查看Update操作,會發現只更新了修改的字段。

  2. 觀察一下前面學學習時候執行的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));
    
  3. 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語句。

  4. EF是跨數據庫的,如果遷移到MYSQL上,就會翻譯成MYSQL的語法。要配置對應數據庫的Entity Framework Provider。

  5. 細節:

    每次開始執行的__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中可能也會不支持。

  1. 不先查詢再修改再保存,而是直接更新部分字段的方法

    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();
    
  2. 不先查詢再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()。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章