MySQL via EF6 的試用報告
公司的專案中用的 ORM 是 Dapper,程式碼中充斥著大量的 SQL 語句,為了少寫 SQL 語句,領導讓我把 EF6 也加進去看會不會有問題。按照指示,我在新的程式碼分支引入了 EF6 並做了 CRUD 的測試,結論是混合使用 Dapper 和 EF6 沒問題。為了讓團隊中沒用過 EF 的同事也能快速上手 EF,我把我的試用記錄重新整理了一下,於是乎就有了本文。
1、如何通過 EF6 來連線 MySQL?
1、安裝 MySQL 的 .NET 驅動
要在 .NET 專案中連線 MySQL 首先得安裝 MySQL 的 .NET 驅動。這個驅動是向下相容的,官方下載地址:MySQL Connector/NET 。
2、安裝 MySql.Data.EntityFramework
Install-Package MySql.Data.EntityFramework -Version 8.0.15
上面的 NuGet 命令會自動幫你把 EF6 和 MySql.Data 都安裝好,無需額外再安裝。
3、建立模型類
有了和資料庫中表對應的模型類,才能方便的操作資料庫而不必寫 SQL 語句。如定義一個 Person 實體,示例如下:
[Table("person")] // 這裡不僅可以自定義表的 Name 還可以自定義表的 Schema public class Person { [Key] public Int32 ID { get; set; } public String Name { get; set; } public DateTime Birthday { get; set; } public Int32 NationID { get; set; } public Nation Nation { get; set; } }
定義實體的注意事項:
- 1、模型類名與表名不必相同。如果不同,則需要用 TableAttribute 標註一下;如果相同,則可以省略該 Attribute。
- 2、主鍵名不必非得是 ID。如果不是,則需要用 KeyAttribute 標註一下;如果是 ID,則可以省略該 Attribute。EF 遵循“約定大於配置”的開發原則,比如 EF 中主鍵名預設為 ID 就是 EF 的一個內建約定,EF 還支援自定義約定。
4、建立資料庫上下文類
有了資料庫上下文,就可以連線資料庫了,然後在上下文中定義相應的 DbSet(實體物件集合),就能直接對資料庫進行 CRUD 操作了。如建立一個 Demo 的上下文,示例如下:
public class DemoDbContext : DbContext { // 宣告 DbSet,實現 CRUD 的方法定義在 DbSet 中 public DbSet<Person> Persons { get; set; } public DbSet<Nation> Nations { get; set; } public DemoDbContext() : base("name=ConnectionString") { // 關閉遷移,EF Code First 預設會在 Model 發生改變後自動更新資料庫 Database.SetInitializer<DemoDbContext>(null); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 解決表名變複數的問題,EF 生成 SQL 語句時預設會將實體名變成複數 modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } }
定義上下文的注意事項:
- 1、建立的資料庫上下文類必須繼承 DbContext 類。
-
2、在上下文類的建構函式中通過 base 的方式指定資料庫連線字串。base 的引數寫法有多種,常見的寫法如下:
base("ConnectionString") base("name=ConnectionString") base(new MySqlConnection("..."), false)
- 3、由於 EF 的遷移功能過於複雜,且非必要,一般不用,在建構函式中關閉即可。
- 4、EF 預設生成的表名是 Model 名的複數,可在 OnModelCreating 中移除該轉換規則。
2、如何通過 EF6 來實現 CRUD?
2.1、Create 新增
- 1、向一個表中新增一條資料,示例如下:
using (var context = new DemoDbContext()) { var p = new Person() { Name = "Andy", Gender = 1 }; context.Persons.Add(p); context.SaveChanges(); // 返回受影響行數 1 }
上面的程式碼會生成 1 條 INSERT 語句和 1 條 SELECT 語句。
- 2、同時向存在主外來鍵的兩個表中新增一條資料,示例如下:
using (var context = new DemoDbContext()) { var n = new Nation() { Name = "China" }; var p = new Person() { Name = "Mark", Gender = 1, NationID = n.ID }; context.Nations.Add(n); context.Persons.Add(p); context.SaveChanges(); // 返回受影響行數 2 }
上面的程式碼會生成 1 條 INSERT 語句和 2 條 SELECT 語句。
- 3、一次新增多個並附加事務:
String connectionString = "server=localhost;port=3306;database=demo;uid=root;pwd="; using (MySqlConnection connection = new MySqlConnection(connectionString)) { connection.Open(); MySqlTransaction transaction = connection.BeginTransaction(); try { using(var context = new DemoDbContext(connection)) { context.Database.UseTransaction(transaction); List<Person> ps = new List<Person>(); ps.Add(new Person { Name = "Mark",Gender = 1 }); ps.Add(new Person { Name = "Jack", Gender = 1 }); ps.Add(new Person { Name = "Tom", Gender = 1 }); context.Persons.AddRange(ps); context.SaveChanges(); } transaction.Commit(); } catch { transaction.Rollback(); throw; } }
2.2、Retrieve 查詢
-
1、EF 查詢支援 LINQ 寫法,必須在最後呼叫
ToList()
才會執行查詢,示例如下:
using (var context = new DemoDbContext()) { context.Database.Log = Console.WriteLine; var list1 = (from p in context.Persons where p.ID == 1 select p).ToList(); var list2 = (from p in context.Persons select p.Name).ToList(); var query = from p in context.Persons select p; query = from p in query where p.ID >= 1 select p; query = from p in query where p.NationID == 1 select p; query = from p in query orderby p.Name descending select p; query.ToList(); }
- 2、EF 查詢支援 Lambda 寫法,示例如下:
using (var context = new DemoDbContext()) { context.Database.Log = Console.WriteLine; // LIMIT 1 var p1 = context.Persons.FirstOrDefault(); // LIMIT 2,不會做引數化處理 var p2 = context.Persons.Single(p => p.ID == 5); // LIMIT 2,會自動做引數化處理 var p3 = context.Persons.Find(3); // 會自動做引數化處理 var p4 = context.Persons.Where(p => p.Name.Contains("Andy")).ToList(); // 只查詢部分資料行,可用這個實現分頁查詢 var p5 = context.Persons.OrderBy(p => p.Name).Skip(3).Take(5).ToList(); // 帶條件的分頁查詢 var p6 = context.Persons.Where(p => p.ID > 0).OrderBy(p => p.Name).Skip(3).Take(5).ToList(); }
- 3、查詢關聯資料,示例如下:
using (var context = new DemoDbContext()) { var persons = context.Persons.Include(p => p.Nation).ToList(); }
上面的程式碼會生成 1 條內連線 SELECT 語句。
2.3、Update 修改
- 1、修改一條確定存在的資料時,用如下語句:
using (var context = new DemoDbContext()) { var p = new Person() { ID = 3, Name = "Andy" }; context.Persons.Attach(p); context.Entry(p).Property(i => i.Name).IsModified = true; context.SaveChanges(); // 返回受影響行數 }
上面的程式碼會生成 1 條 UPDATE 語句,資料不存在時會報錯。
- 2、如果需要確認資料存在後再修改的話,用如下語句:
using (var context = new DemoDbContext()) { var p = context.Persons.Find(1); // 也可以用 FirstOrDefault 或其它查詢方法 if (p != null) { p.Name = "Peter"; context.Persons.Attach(p); context.Entry(p).Property(i => i.Name).IsModified = true; // 指定更新欄位 context.SaveChanges(); // 返回受影響行數 } }
上面的程式碼會生成 1 條 UPDATE 語句和 1 條 SELECT 語句。
2.4、Delete 刪除
- 1、刪除一條確定存在的資料時,用如下語句:
using (var context = new DemoDbContext()) { var p = new Person() { ID = 1 }; context.Persons.Attach(p); context.Persons.Remove(p); context.SaveChanges(); // 返回受影響行數 }
上面的程式碼會生成 1 條 DELETE 語句,資料不存在時會報錯。
- 2、如果需要確認資料存在後再刪除的話,用如下語句:
using (var context = new DemoDbContext()) { var p = context.Persons.FirstOrDefault(it => it.ID == 1); if (p != null) { context.Persons.Attach(p); context.Persons.Remove(p); context.SaveChanges(); } }
3、如何更好的運用 EF6 來完成工作?
技術好的人經常講業務場景,相反,有些技術差的人卻喜歡不由分說的吐槽那些他根本就沒搞懂的技術。在 .NET 圈子裡,有人對 EF 是愛不釋手,也有人對 EF 是各種吐槽。
我很喜歡的一句話是:“沒有不好的技術,只有沒被用好的技術”,我的理解是任何技術都有侷限性,作為程式設計師,我們要做的是結合實際業務場景來選用最合適的技術。要想在專案中更好的運用 EF,就得更多的瞭解 EF 技術,本節就來分享一下我試用 EF6 過程中的一些收穫。
3.1、傳說中 EF 的三種模式
為什麼說 EF 的三種模式是傳說呢?因為新版的 EF 預設只支援 Code First 這一種模式了。要想用 Database First 或 Model First 還得把 Visual Studio 降級到 VS10 或 VS12 才行,實在沒必要,下面簡單羅列下每種模式的特點:
- 1、Database First :即資料庫優先,先建立好資料庫和表,然後自動生成 EDM(實體資料模型)檔案,再由 EDM 檔案生成模型類。當現有資料庫結構比較成熟穩定時,可用這種模式實現快速開發。
- 2、Model First :即模型優先,先建立視覺化的 EDM 檔案,然後由 EDM 檔案來自動生成模型類和資料庫。開發速度快,但程式碼冗餘。寫個小 Demo 還行,但企業級開發一般沒人用這個模式。
- 3、Code First :即程式碼優先,先寫好模型類,然後自動生成資料庫,沒有 EDM 檔案。程式碼簡潔可控,也是官方和業界首推的模式。
3.2、EF6 執行原生 SQL 查詢
總會有些時候,我們為了效能或者其它各種各樣的緣故,而不得不寫 SQL 語句,EF 提供了直接執行 SQL 語句的方法SqlQuery()
。
- 1、執行無引數的原生 SQL 查詢,示例如下:
using (var context = new DemoDbContext()) { var persons = context.Persons.SqlQuery("SELECT * FROM Person").ToList(); }
- 2、執行帶引數的原生 SQL 查詢,示例如下:
using (var context = new DemoDbContext()) { var sql = "SELECT t.* FROM Person t WHERE t.Gender=@Gender"; var p1 = context.Persons.SqlQuery(sql, new MySqlParameter("@Gender", 1)).ToList(); // 下面這種更簡單的寫法相當於上面兩句,EF 會自動將其轉換為引數化查詢 var p2 = context.Persons.SqlQuery("SELECT t.* FROM Person t WHERE t.Gender={0}", 1).ToList(); }
- 3、只查詢部分可選欄位,示例如下:
using (var context = new DemoDbContext()) { var persons = context.Database.SqlQuery<MiniPerson>("SELECT t.ID,t.Name FROM Person t").ToList(); }
注意:這裡用的是MiniPerson
類,而不是模型類Persons
,因為用模型類時,查詢返回的欄位必須與其模型中的欄位對應,而用非模型類時則沒有這個限制,EF 會自動把值賦給相應的欄位,並忽略其它欄位,即便完全不匹配也不會報錯。
- 4、統計表中的資料條數,示例如下:
using (var context = new DemoDbContext()) { var count = context.Database.SqlQuery<Int32>("SELECT COUNT(1) FROM Person").SingleOrDefault(); }
其實 EF 的SqlQuery()
還支援呼叫儲存過程,但實際開發中,一般最好不要儲存過程。因為一旦用了儲存過程,相比較得到的效能提升,往往付出的維護代價會更大,得不償失。
3.3、EF6 執行原生 SQL 增刪改
EF6 呼叫增刪改等命令語句的方法是ExecuteSqlCommand()
,示例如下:
using (var context = new DemoDbContext()) { context.Database.ExecuteSqlCommand("INSERT INTO Person VALUES(DEFAULT,'小明',NOW(),1)"); context.Database.ExecuteSqlCommand("UPDATE Person SET Name='小王' WHERE ID=8"); context.Database.ExecuteSqlCommand("DELETE FROM Person WHERE ID=14"); }
一般用 EF 就是為了不寫 SQL 語句,尤其是大多數時候不會造成效能問題的增刪改語句,所以使用ExecuteSqlCommand()
的概率是比較低的。
3.4、EF6 不推薦的 CRUD 寫法
有些朋友通過別人的帖子發現直接更改實體狀態也能修改資料,然後就一直這麼用。但如果你不是很瞭解 EF 的實體狀態管理機制,就很可能會給自己挖坑,所以一般不推薦這種 CRUD 的寫法。
我多次看到網上有人問諸如 EF 改了資料儲存報錯之類的問題,基本都是他自己還沒搞清楚 EF 各個實體狀態的含義,然後就在那兒強制更改實體狀態,然後遇到坑自己還解決不了。這種做法有可能還會破壞 EF 的樂觀併發控制,而且有些版本也不支援這種做法。下面給出兩個負面案例:
- 1、不推薦的修改寫法,會更新所有欄位,示例如下:
using (var context = new DemoDbContext()) { context.Database.Log = Console.WriteLine; var p = new Person() { ID = 3, Name = "Andy" }; context.Entry(p).State = EntityState.Modified; context.SaveChanges(); // 返回受影響行數 1 }
上面的程式碼會生成 1 條 UPDATE 語句。
- 2、不推薦的刪除寫法,示例如下:
using (var context = new DemoDbContext()) { var p = new Person() { ID = 1 }; context.Entry(p).State = EntityState.Deleted; context.SaveChanges(); // 返回受影響行數 1 }
上面的程式碼會生成 1 條 DELETE 語句。
3.5、EF6 效能優化
-
1、非跟蹤查詢 AsNoTracking
預設情況下,EF 會一直跟蹤實體的狀態,這也是為什麼當我們呼叫
SaveChanges()
的時候,EF 能夠把最終的資料狀態準確提交到資料庫的原因。但有些時候,我們查詢出資料只是為了做展示,並不需要修改或刪除,這時候就可以呼叫AsNoTracking()
來使得物件為 Detached 狀態,之後 EF 就不再跟蹤這個物件狀態了,在合適的場景下能顯著提升效能。
using (var context = new DemoDbContext()) { // 查詢所有人並且不跟蹤他們的狀態 var p1 = context.Persons.AsNoTracking().ToList(); // 查詢部分人並且不跟蹤他們的狀態 var p2 = context.Persons.Where(i => i.NationID == 1).AsNoTracking().ToList(); }
- 2、EF 預設是開啟了 LoayLazy 的,別手賤關了就行。如下是預設配置:
this.Configuration.ProxyCreationEnabled = true; this.Configuration.LazyLoadingEnabled = true;
3.6、EF6 開發及除錯技巧
- 1、如果想知道 EF 會執行什麼 SQL 語句,比如是控制檯專案,在執行程式碼塊中增加如下語句即可:
context.Database.Log = Console.WriteLine;
- 2、如果是自己測試,可以讓 EF 每次都根據程式碼更新資料庫,在上下文建構函式中增加如下程式碼即可:
// 當資料庫模型發生改變時,則刪除當前資料庫,重建新的資料庫(實際開發中永遠不要這麼寫,太危險了) Database.SetInitializer(new DropCreateDatabaseIfModelChanges<EFDbContext>());
或者在 CRUD 程式碼塊中加入如下程式碼,僅當資料庫不存在時,才由 EF 建立資料庫:
context.Database.CreateIfNotExists();
4、總結
本文主要講解了如何快速上手 EF6 和基本的 CRUD 操作。用 .NET 技術的博友都知道,如今 .NET 陣營除了經典的 .NET Framework 之外,還有一個開源版的 .NET Core。對應的,EF 也適時地推出了 EF Core 版,如果你的專案是 .NET 的,那就繼續用 EF6 吧,畢竟是久經考驗的版本,而 EF Core 是全新開發的,更適合 .NET Core 型別的專案。而且官方也說從 EF6 到 EF Core 是移植而不是升級。
4.1、MySQL 官方元件的用途說明
- 1、mysql-connector-net :MySQL Connector/NET 是 MySQL 官方的 .NET 驅動程式,或者說是 MySQL for .NET 的客戶端開發包,其中包含了 .NET 連線 MySQL 所必須的 dll 檔案。
- 2、mysql-for-visualstudio :6.7 以下版本的驅動中會包含該元件,它的作用是在通過 VS 建立實體模型時,在資料來源中增加 MySQL 型別選項。如果只用 Code First,那麼就不需要該元件了。
- 3、mysql-connector-odbc :MySQL Connector/ODBC 使得使用者可以通過 ODBC(Open Database Connectivity,開放資料庫互聯)來連線 MySQL 伺服器。
4.2、本文 Demo 的程式碼補充說明
- 文中的 Nation 實體定義如下:
public class Nation { public Int32 ID { get; set; } public String Name{ get; set; } }
- 文中的 MiniPerson 類定義如下:
public class MiniPerson { public Int32 ID { get; set; } public String Name { get; set; } }
本文連結:http://www.cnblogs.com/hanzongze/p/ef6-trial-report.html
版權宣告:本文為部落格園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文連結!個人部落格,能力有限,若有不當之處,敬請批評指正,謝謝!