EntityFramework使用及優化

風靈使發表於2018-06-11

1、 簡介

  • ORM框架:Object Relation Mapping,用操作物件的方式來運算元據庫
  • 其它框架:DapperNHibernate,首推EF,微軟官方的。
  • EF底層還是ADO.NET實現的。
  • EF支援SqlServerMySQLOracle等主流資料庫
  • 使用EF開發資料庫有兩種形式:先建資料庫or先建模型類
  • 三種模式:
  • DataBase First資料庫優先;先建資料庫表結構,生成EDM檔案
    Model First模型優先;沒啥用
    Code First:程式碼優先;開發者自己寫模型生成資料庫,沒有EDM檔案

    DataBase First:簡單、方便,但如果是大專案後期會非常痛苦,會出現諸如:修改了資料庫在程式中更新EF不起作用等這類問題。

    Code First:門檻高,但適合大專案,微軟主推;資料庫由EF幫助生成,當修改模型後,EF使用DB Miguration自動幫助修改資料庫,但也可以禁用Miguration,手動建立(推薦)

  • EF是採用約定大於配置的框架原則的,能遵守約定的就不要去配置

2、EF的安裝

①程式中右鍵新建項
②通過NuGet程式集:Install-Package EntityFramework

會自動在App.config中增加兩個entityFramework配置段,如果是MySql資料庫還需要新增相關的MySql的驅動。

Web.config中配置連線字串

<connectionStrings>

    <add name="connStr" connectionString="Data source=.;initial catalog=School;user id=sa;password=***;" providerName="System.Data.SqlClient" />

  </connectionStrings>

3、EF簡單實體配置DataAnnotations

資料庫建表T_Persons

程式中實體類Person

[Table("T_Persons")]//表名與類名不一樣,所以需要特性標註

public class Person{

        public long Id { get; set; }

        public string Name { get; set; }

        public DateTime CreateDateTime { get; set; }

}

備註:因為EF約定主鍵欄位時Id,所以不用再特殊指定Id是主鍵;主鍵必須是Id,可以配置但不推薦
常用特性:必填欄位[Required]、限制欄位長度[MaxLength(5)]、欄位在資料庫中有預設值[DatabaseGenerated]、可空欄位int?用問號修飾、欄位需要用public

//建立實體類

public class TestDbContext:DbContext{

        public TestDbContext()

            : base("name=connStr")

        {

        }

        //通過對Persons操作就可以完成對T_Persons表的操作

        public DbSet<Person> Persons { get; set; }

}

是否using的爭議
不using也沒問題,但其實using更好。推薦using

異常處理:

InnerException

4、EF模型的兩種配置方式

①DataAnnotaions;方便,偶爾度高,上邊那種

②FluentAPI;微軟推薦使用的

5、FluentAPI使用

①sql中建表

②C#建模型類

③建立PersonConfig

using System.Data.Entity.ModelConfiguration;

public class PersonConfig:EntityTypeConfiguration<Person>{

        public PersonConfig() {

            this.ToTable("T_Persons");

        }

}

④建立DbContext

public class MyContext:DbContext{

        public MyContext() : base("name=connStr") {



        }

        public DbSet<Person> Persons { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder){

            base.OnModelCreating(modelBuilder);

    //當前程式碼所在程式集,載入所有的繼承自EntityTypeConfiguration為模型配置類  modelBuilder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly());

        }

}

當存在多表時,只要新增多個實體類與DbSet就可以了

6.、EF的原理及sql監控

EF會自動把whereOrderBySelect等這些編譯成“表示式樹(Expression Tree)”,然後會把表示式樹翻譯成SQL語句執行。因此不是把資料都取到記憶體中,然後使用集合的方式進行資料過濾,因此效能不會低。但如果這個操作不能被翻譯成sql語句,則會報錯,或者被放在記憶體中操作,效能就會很低。

檢視真正執行的sql是什麼樣的

MyContext ctx = new MyContext();

ctx.Database.Log = (sql) =>{

                Console.WriteLine("=============Log============" + sql);

            };

EF的延遲執行

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

各種語句編譯後的結果:

Person p=ctx.Persons.First();

p.Name = "hello word";

ctx.SaveChanges();

Log:
這裡寫圖片描述

這裡寫圖片描述

EF很智慧,知道我只是更新Name欄位,而且主動的幫我找到ID;

值得一提的是如果你重複執行語句,那麼EF也很聰明的不會執行該update語句…

ctx.Persons.Where(o => o.Name.StartsWith("baidu")).ToList();

這裡寫圖片描述

var result = ctx.Persons.Where(p => p.Name.Contains("com"));

則是%com%

var result = ctx.Persons.Where(p => p.Name.Length > 5).ToList();

這裡寫圖片描述
該語句生成的sql相對複雜些,而且欄位上涉及到了資料型別轉換(將得到的欄位值轉換成int型別再比較),它的執行效率就比較低。

var result = ctx.Persons.Where(p => p.CreateDateTime > DateTime.Now).ToList();

這裡寫圖片描述

long[] ids = { 2, 5, 6 };

var result = ctx.Persons.Where(p => ids.Contains(p.Id)).ToList();

EF很強大,自動幫助拼接了字串

IEnumerable<Person> p = ctx.Persons.Where(o => o.Id > 3);

            p=p.Where(o => o.Name.Length > 3);

            p.ToList();

這裡寫圖片描述
第一次獲得IEnumerable<Person> p時,已經把資料載入入記憶體了,又在記憶體中操作效率就很低。

IQueryable<Person> p = ctx.Persons.Where(o => o.Id > 3);

            p=p.Where(o => o.Name.Length > 3);

            p.ToList();

這裡寫圖片描述
使用IQueryable<Person> 物件時,所有的操作都是在資料庫中執行的,最後返回給程式

如果使用IEnumerable<Person>時,是把所有id>3的都載入到記憶體中又Linq to Object查詢了一次這樣非常不好。

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

細節:
每次開始執行的__MigrationHistory 等這些 SQL 語句是什麼?

DBMigration 用的,也就是由 EF 幫我們建資料庫,現在用不到,

通過下面的程式碼禁用:

Database.SetInitializer<XXXDbContext>(null);

XXXDbContext 就是專案 DbContext 的類名。一般建議放到 XXXDbContext 建構函式中。
注意這裡的 DatabaseSystem.Data.Entity 下的類,不是 DbContextDatabase 屬性。如果寫到 DbContext 中,最好用上全名,防止出錯。

7、EF執行sql

在一些特殊場合,需要執行原生 SQL。比如Sqlserver有一些特有的函式,EF就無法使用語句生成,因為EF是垮資料庫的,不能支援所有的特性。
執行非查詢語句,呼叫 DbContextDatabase 屬性的ExecuteSqlCommand 方法,可以通過佔位符的方式傳遞引數:

ctx.Database.ExecuteSqlCommand("update T_Persons set Name={0},CreateDateTime=GetDate()",
"baidu.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 介面
類似於 ExecuteScalar 的操作比較麻煩:
int c = ctx.Database.SqlQuery<int>("select count(*) from T_Persons").SingleOrDefault();

8、EF中不是所有lambda寫法都能被支援

var result = ctx.Persons.Where(p => Convert.ToString(p.Id)=="3");

出現“System.NotSupportedException”異常一般就說明你的寫法無法翻譯成 SQL 語句。

想獲取建立日期早於當前時間一小時以上的資料:

var result = ctx.Persons.Where(p => (DateTime.Now - p.CreateDateTime).TotalHours>1);

同樣也可能會報System.ArgumentException 型別的未經處理的異常

也就是說在EF使用lambda表示式中不能發生,計算or型別轉換操作

EF中提供了一個SqlServer專用的類,SqlFunctions,這個方法只對Sqlserver資料庫支援,對於在EF不支援的函式提供支援;

比如:

var result = ctx.Persons.Where(p =>

SqlFunctions.DateDiff("hour",p.CreateDateTime,DateTime.Now)>1);

9、EF物件狀態管理

為什麼查詢出來的物件 Remove()、再 SaveChanges()就會把資料刪除。 而自己 new 一個Person()物件,然後 Remove()不行?
為什麼查詢出來的物件修改屬性值後、再 SaveChanges()就會把資料庫中的資料修改。
因為 EF 會跟蹤物件狀態的改變。
EF 中物件有五個狀態:

Detached(遊離態,脫離態) 、

Unchanged(未改變) 、Added(新增) 、Deleted(刪除) 、 Modified(被修改)

狀態的裝換
這裡寫圖片描述

Add()Remove()修改物件的狀態。 所有狀態之間幾乎都可以通過: Entry(p).State=xxx 的方式進行強制狀態轉換。狀態改變都是依賴於 Id 的( Added 除外)

應用
SavaChanged()方法執行期間,會檢視當前物件的 EntityState 的值,決定是去新增( Added)、修改( Modified)、刪除( Deleted)或者什麼也不做( UnChanged)。

10、EF優化的一個技巧

如果查詢出來的物件只是供顯示使用,不會修改、刪除後儲存,那麼可以使用
AsNoTracking()來使得查詢出來的物件是 Detached 狀態,這樣對物件的修改也還是 Detached狀態, EF 不再跟蹤這個物件狀態的改變,能夠提升效能。

var p1 = ctx.Persons.Where(p => p.Name == "baidu.com").FirstOrDefault();
Console.WriteLine(ctx.Entry(p1).State);

//改成:

var p1 = ctx.Persons.AsNoTracking().Where(p => p.Name == "baidu.com").FirstOrDefault();
Console.WriteLine(ctx.Entry(p1).State);

因為 AsNoTracking()DbQuery 類( DbSet 的父類)的方法,所以要先在 DbSet 後呼叫AsNoTracking()

如果確實還想再更新,ctx.Entry().State=System.Data.Entity.EntityState.Unchanged;後在更新即可。

相關文章