EntityFramework使用及優化


1、 簡介

  ORM框架Object Relation Mapping,用操作對象的方式來操作數據庫

  其它框架Dapper、NHibernate,首推EF,微軟官方的。

  EF底層還是ADO.NET實現的。

  EF支持SqlServer、MySQL、Oracle等主流數據庫

  使用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會自動把where、OrderBy、Select等這些編譯成“表達式樹(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 構造函數中
注意這里的 Database System.Data.Entity 下的類,不是 DbContext Database 屬性。如果寫到 DbContext 中,最好用上全名,防止出錯。

 

7、EF執行sql

在一些特殊場合,需要執行原生 SQL。比如Sqlserver有一些特有的函數,EF就無法使用語句生成,因為EF是垮數據庫的,不能支持所有的特性。
執行非查詢語句,調用 DbContext Database 屬性的 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;后在更新即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM