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;后在更新即可。