前幾篇筆記提到 EF Core 主打 Code First 概念,講求先定義 Model 再自動產生資料表 Schema 的運作模式。

熟悉傳統資料庫程式開發方式的老鳥們看完猛搖頭:為什麼把事情搞到這麼複雜? 先定義 Schema 文件直接決定關聯建好資料庫,開發者照著規格寫程式不是比較簡單明瞭? 為什麼要搞出一堆 Attribute、Fluent API 間接決定 Index、Primary Key、Foreign 庸人自擾?

EF Code First 有個傳統資料庫程式開發所沒有的特異功能 - 跨資料!

這一篇將試著用一個範例展示 EF Core Code First 跨資料庫的威力, 示範如何只改一行程式,就讓同一支程式在 SQLite、SQL Server 及 Oracle 間自由切換。 看完大家可以評估投資時間學習及構思有點複雜性的 EF Model 設計是否值得。

我開了一個 .NET Core Console Application 專案,透過 NuGet 安裝四個套件:

  1. Microsoft.EntityFrameworkCore.Sqlite (SQLite 資料庫使用)
  2. Microsoft.EnittyFrameworkCore.SqlServer (SQL Server 使用)
  3. Oracle.EntityFrameworkCore (Oracle 資料庫使用)
  4. Microsoft.EntitryFrameworkCore.Design (產生 Migrations Script 用)

接著設計一個簡單的 DbContext:

#define UseOracle
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;

namespace CrossDB
{
    class MyContext : DbContext
    {
        public enum EFDataProviders
        {
            Sqlite, SQL, Oracle
        }

        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
#if UseSqlite
            optionsBuilder.UseSqlite("Data Source=blogging.db");
#elif UseSql
            optionsBuilder.UseSqlServer(
@"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=X:\Path\Blog.mdf;Integrated Security=True;Connect Timeout=30");
#elif UseOracle
            optionsBuilder.UseOracle(@"data source=XE;user id=jeffrey;password=****;");
#endif 
        }

    }

    public class Blog
    {
        public int BlogId { get; set; }
        public string Url { get; set; }
        public List<Post> Posts { get; set; }
    }

    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public Blog Blog { get; set; }
    }
}

Blog 與 Post Entity 類別取自官方文件,在前面幾篇筆記裡反覆登場,大家應該不陌生(堪稱於 EF Core 界的 Hello World 級 Entity)。 唯一要提的是在 OnConfiguring() 裡我用 #define#if 條件化編譯以更換資料來源。 第一行寫 #define UseSqlite 會執行 optionsBuilder.UseSqlite();改成 #define UseSqlServer 會執行 optionsBuilder.UseSqlServer(); 再改成 #define UseOracle 則執行 optionsBuilder.UseOracle(),藉此切換不同資料庫。

依據先前學過的 Migration 技巧,我先 #define UseSqlite 指定使用 SQLite,編譯後分別執行 dotnet ef migrations add InitialCreate 及 dotnet ef database update,EF Core 會幫我們新增好 SQLite 資料庫檔案,並在其中建好資料表。

在 Main() 寫一小段程式,先檢查資料表,若是空的先新增一筆 Blog、一筆 Post,並讓二者關聯。接著用查詢資料庫將資料讀出來:

using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
namespace CrossDB
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var cbx = new MyContext())
            {
                if (!cbx.Blogs.Any())
                {
                    var blog = new Blog()
                    {
                        Url = "https://blog.darkthread.net"
                    };
                    cbx.Blogs.Add(blog);
                    cbx.SaveChanges();
                    var post = new Post()
                    {
                        Blog = blog,
                        Title = "Hello World!",
                        Content = "My first blog post"
                    };
                    cbx.Posts.Add(post);
                    cbx.SaveChanges();
                }

                var b = cbx.Blogs.Include(o => o.Posts).First();
                Console.WriteLine(b.Url);
                var p = b.Posts.First();
                Console.WriteLine(p.Title);
                Console.WriteLine(p.Content);
                Console.ReadLine();
            }
        }
    }
}

執行程式,EF Core 順利地寫入資料並正確讀出,加上 Include(o => o.Posts) 使 Blog 查詢時一併帶出其所屬 Post; Post 有個 BlogId 欄位指向 Blog 的 BlogId,在 Blog 與 Post 間建立關聯。 使用第三方 SQLite Database Browser 查詢 SQLite 資料庫,可以在其中看到 Blogs 與 Posts 資料表,也可在其中查到 Post 資料。

接著調為 #define UseSqlite 改用 SQL Server,編譯後重跑 dotnet ef database update,這回 EF Core 會在 SQL LocalDB 建好資料表。 重跑測試,照樣成功寫入資料並正確讀取,但這次資料是在 SQL。 用 Visual Studio Server Explorer 觀察 EF Core 建立的 Schema,可以看到 Post 的 PostId 設了 IDENTITY(1,1) 自動跳號 [1], BlogId 欄位設了 FOREIGN KEY [2],且建了 NONCLUSTER INDEX [3]:

最後改成 #define UseOracle 再依前述步驟重跑一次,一樣能成功寫入資料並正確讀取,但資料庫這回已改成 Oracle。 在 SQLite 與 SQL Server 上,Blog 與 Post 的 Primary Key (BlogId 與 PostId) 都是採自動跳號, 而 EF Core 在 Oracle 建立的 Post 資料表(如下圖),PostId 預設值被設成 ISEQ$$_73582.nextval, 也用了 Sequence 自動跳號:

以上實驗示範了 EF Core Code First 能實現「設計階段不考慮資料庫種類,調一行程式就換一種資料庫」的威力。

當然,在江湖打滾過的老鳥才不會只看這些就天真以為能從此過著幸福快樂的日子。 魔鬼在細節裡,實際處理跨資料庫細部設計時仍需要面對一堆眉角,但本質上 EF Core 已奠定不錯的隔離基礎,讓開發者能更輕鬆擺脫特定資料的庫束縛,在必須跨資料庫的應用情境將是很犀利的武器。

Demostration of how to run the same EF Core code first programe smoothly with SQLite, SQL Server and Oracle data provider.


Comments

# by Mars

想多了解 EF 的效能到底如何提升,例如一次要塞幾千幾萬筆這種狀況到底要怎樣才可以提高效率?

# by Sa

>Mars 試試看Z.EntityFramework.Plus.EFCore?

# by Jeffrey

to Sa, 哦哦哦,Z.EntityFramework.Plus.EFCore https://github.com/zzzprojects/EntityFramework-Plus 是好東西,已筆記,感謝!

# by Sting

很多推Code First的老師都會說類似 : EF Core Code First 能實現「設計階段不考慮資料庫種類,調一行程式就換一種資料庫」... 但這輩子會遇到幾次系統原本是Oracle DB要改成SQL SERVER? 如果只為這理由來使用Code First技術,值得嗎?

# by Tony Tsai

通常不會真的遇到要 `換資料庫` ,不過EF Core migration的功能可以針對model的變化,利用 `cli` 產生變更的sql語法,或是直接更新資料庫schema。 DB First的情境,就是先改資料庫,在使用DB First回來更新 Entites,上版正式環境的時候,要手動準備alter等等的指令。feature小還好,feature一大的時候,沒有盤點好SQL就GG。 Code First的情境,先改Entities,測試環境下database update直接更新所有schema,正式環境就是下指令output script,檢查這份SQL上正式機。 都還是有些利弊,會有某種技術的產生就是為了解決某種問題

# by Leon

用 ORM 把資料庫操作封裝成程式碼最大的好處應該是 time to market 的時間可以大幅縮短,對 RD 的人力配置也可以更精簡,不用資深 DBA DBM,能一秒換 DB 系統這的確不是實際場景上常看到的優勢。 在 Rails、Laravel、Django 三大主流 web 框架都已經內建 ORM,運用 ORM 是目前開發的主流,一直到效能發生瓶頸才會考慮自行優化 DB 查詢、磁碟 IO、OS 核心等工作,以 Twitter 為例,它們在超過五千萬用戶左右才把 Rails 換掉換成自主架構。

# by 里克威斯特

hi, 在前面的留言有提到這個套件 https://github.com/zzzprojects/EntityFramework-Plus 它的github裡顯示授權是MIT license, 但在README的說明中指出This library is powered by Entity Framework Extensions, 而Entity Framework Extensions是要付費的, 這讓我看不懂, 它倒底能不能免費商用? 我想把它的批量更新或批量刪除功能, 用在替公司開發的SaaS系統中

Post a comment