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
版權聲明:本文為博客園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文鏈接!個人博客,能力有限,若有不當之處,敬請批評指正,謝謝!


免責聲明!

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



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