" 1、如何通過 EF6 來連接 MySQL? " " 2、如何通過 EF6 來實現 CRUD? " "2.1、Create 添加" "2.2、Retrieve 查詢" "2.3、Update 修改" "2.4、Delete 刪除" " 3、如何更好的運用 EF6 來完成工作? " "3.1、傳說中 ...
公司的項目中用的 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
版權聲明:本文為博客園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文鏈接!個人博客,能力有限,若有不當之處,敬請批評指正,謝謝!