The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

關於 Entity Framework Core 獨立放在 DAL 專案的注意事項

記得我在 13 年前寫過一篇 關於 Entity Framework 獨立放在 DAL 專案的注意事項 文章,今天我想來寫另一篇關於 Entity Framework Core 獨立放在單獨專案的注意事項,幫助大家更好的架構 .NET 應用程式。

啟始專案

基礎的 Entity Framework Core 開發過程我就不贅述了,我們直接從一個已經切好兩個專案的方案檔開始,請透過以下命令取得我已經建立好 Entity Framework Core 資料模型的方案。

  1. 下載範例程式

    git clone https://github.com/doggy8088/WebApiEFCoreUnableCreateController.git
    cd WebApiEFCoreUnableCreateController
    
  2. 用 Visual Studio 2022 開啟 WebApiEFCoreUnableCreateController.sln 方案檔

    image

我的範例程式有兩個專案:

  1. WebApiEFCoreUnableCreateController.Domain

    這是一個 Entity Framework Core 7.0 專案,裡面包含實體模型類別(Entity model class)與資料內容類別(DbContext class),專案檔中只有安裝 Microsoft.EntityFrameworkCore.SqlServer 套件,程式碼是透過 EF Core Power Tools 自動產生的,目前還沒有任何其他設定。

  2. WebApiEFCoreUnableCreateController

    這是一個 ASP.NET Core 7.0 的 Web API 專案,已經將 WebApiEFCoreUnableCreateController.Domain 專案加入參考,除此之外沒有任何其他設定。

地雷一:無法透過 VS2022 快速建立 API Controller

若你只是單純的在另一個獨立專案建立 Entity Framework Core 資料模型,預設是沒辦法透過 Visual Studio 2022 快速建立 API Controller 的,其建立的步驟如下:

  1. 在 Controllers 資料夾執行 Add > Controllers

    image

  2. 選擇 API > API Controller with actions, using Entity Framework 項目範本

    image

  3. Model class 選擇 Course,DbContext class 選擇 ContosoUniversityContext

    image

  4. 此時你就會看到以下錯誤

    Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions`1[WebApiEFCoreUnableCreateController.Domain.Data.ContosoUniversityContext]' while attempting to activate 'WebApiEFCoreUnableCreateController.Domain.Data.ContosoUniversityContext'.

    image

地雷二:無法透過 dotnet-ef 快速建立 Migrations

  1. 要使用 dotnet ef 工具之前,記得要先安裝 Microsoft.EntityFrameworkCore.Design 套件

    dotnet add package Microsoft.EntityFrameworkCore.Design
    
  2. 透過 dotnet ef migrations add 建立一個 Init 資料庫移轉(Migration)就會出現問題

    dotnet ef migrations add init
    

    Unable to create an object of type 'ContosoUniversityContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

    image

這個地雷有兩個 Workaround (應變措施) 可以套用:

  1. 指定 --startup-project-s 參數,指定啟動專案的資料夾路徑

    這個參數可以指定你要從哪個專案取得 DbContext 實例(Instance),因為我們的 WebApiEFCoreUnableCreateController 專案是一個 ASP.NET Core 專案,所以這樣指定可以明確取得該專案所設定的連接字串DbContextOptions<T> 物件:

    dotnet ef migrations add init -s ..\WebApiEFCoreUnableCreateController
    
  2. 改用 Visual Studio 2022 的 Package Manager Console (套件管理員主控台) 的 EF Core tools (PowerShell)

    要使用 Add-Migration Cmdlet 命令,記得要先安裝 Microsoft.EntityFrameworkCore.Tools 套件

    dotnet add package Microsoft.EntityFrameworkCore.Tools
    

    Package Manager Console (套件管理員主控台) 執行以下命令

    Add-Migration Init
    

    這個命令是可以順利建立 Migration 的!

解決方案

由於我們的 Entity Framework Core 資料模型被定義在一個獨立的專案下,跟我們的 Web API 應用程式分別隸屬於兩個不同的專案,所以無法從 application service provider 讀到 DbContext 相關資訊。

我們在執行 dotnet ef migrations add init 事實上有看到一個參考連結連到 設計階段 DbContext 建立 文件,裡面就有提到兩種可能的解決方案。

  1. 使用不含參數的建構函式 (Using a constructor with no parameters)

    這個解決方案則是在 DbContext 加入一個無參數的建構式,以及 override 一個 OnConfiguring 方法即可。

    public ContosoUniversityContext() { }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=ContosoUniversity;Integrated Security=True");
        }
    }
    

    事實上,如果你日後還打算用 Database First 這樣的工作流程來開發 Entity Framework Core 的話,你其實不應該直接修改 ContosoUniversityContext.cs 檔案的內容,否則在日後重新產生資料模型時,修改的內容就會被蓋掉。

    我有試著將上面這段 Code 放到一個獨立的 partial class 中,結果執行 dotnet ef migrations add init 命令時,依然會出現一樣的錯誤訊息 (原因不明),所以這個解決方案其實不夠完美!

  2. 從一個設計時期的工廠產生 (From a design-time factory)

    這個解決方案需要另外建立一個實作 IDesignTimeDbContextFactory<T> 介面的工廠類別,專門提供給像是 dotnet ef 這樣的設計時期工具使用,其程式碼範例如下:

    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Design;
    
    namespace WebApiEFCoreUnableCreateController.Domain.Data;
    
    public partial class ContosoUniversityContextFactory : IDesignTimeDbContextFactory<ContosoUniversityContext>
    {
        public ContosoUniversityContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ContosoUniversityContext>();
    
            optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=ContosoUniversity;Integrated Security=True");
    
            return new ContosoUniversityContext(optionsBuilder.Options);
        }
    }
    

    注意: 這個 IDesignTimeDbContextFactory<T> 介面來自於 Microsoft.EntityFrameworkCore.Design 套件。

    我認為這個解決方案比較完美,可以應付各種狀況,加上這段程式碼之後,我們在 Web API 專案中也可以快速建立 API Controller,完全不會有錯誤發生!

相關連結

留言評論