MySQL via EF6 的試用報告
1、如何通過 EF6 來連接 MySQL?
2、如何通過 EF6 來實現 CRUD?
2.1、Create 添加
2.2、Retrieve 查詢
2.3、Update 修改
2.4、Delete 刪除
3、如何更好的運用 EF6 來完成工作?
3.1、傳說中 EF 的三種模式
3.2、EF6 執行原生 SQL 查詢
3.3、EF6 執行原生 SQL 增刪改
3.4、EF6 不推薦的 CRUD 寫法
3.5、EF6 性能優化
3.6、EF6 開發及調試技巧
4、總結
4.1、MySQL 官方組件的用途說明
4.2、本文 Demo 的代碼補充說明
公司的項目中用的 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());
或者在 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
版權聲明:本文為博客園博主 韓宗澤 原創