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

記得我在 13 年前寫過一篇 [關於 Entity Framework 獨立放在 DAL 專案的注意事項](/post/2010/06/17/Entity-Framework-DAL-metadata-resource) 文章，今天我想來寫另一篇關於 Entity Framework Core 獨立放在單獨專案的注意事項，幫助大家更好的架構 .NET 應用程式。

### 啟始專案

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

1.  下載範例程式
    
    ```bash
    git clone https://github.com/doggy8088/WebApiEFCoreUnableCreateController.git
    cd WebApiEFCoreUnableCreateController
    ```
    
2.  用 Visual Studio 2022 開啟 `WebApiEFCoreUnableCreateController.sln` 方案檔
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/aef20df24633643bdabd-ede2c277-b229-42ae-8ce9-e971b8210eb7.webp)
    

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

1.  `WebApiEFCoreUnableCreateController.Domain`
    
    這是一個 Entity Framework Core 7.0 專案，裡面包含**實體模型類別**(Entity model class)與**資料內容類別**(DbContext class)，專案檔中只有安裝 `Microsoft.EntityFrameworkCore.SqlServer` 套件，程式碼是透過 [EF Core Power Tools](https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools) 自動產生的，目前還沒有任何其他設定。
    
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](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/85d8429a6cb50743b5fb-a1cd6518-d5d8-4099-a2c4-787cc8b63f0e.webp)
    
2.  選擇 API > API Controller with actions, using Entity Framework 項目範本
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/0564fc74b0cdfe8227b4-81891aad-9eab-46dd-b417-1c0d34a886d1.webp)
    
3.  Model class 選擇 `Course`，DbContext class 選擇 `ContosoUniversityContext`
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/1fabe1184d441c5f4173-e4c2d2f0-668b-4e4c-a2c9-d8433946d6be.webp)
    
4.  此時你就會看到以下錯誤
    
    > Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions\`1\[WebApiEFCoreUnableCreateController.Domain.Data.ContosoUniversityContext\]' while attempting to activate 'WebApiEFCoreUnableCreateController.Domain.Data.ContosoUniversityContext'.
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/db7d2d900668126a71f9-3c02d370-10c7-4ac0-950c-bc2f98782446.webp)
    

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

1.  要使用 `dotnet ef` 工具之前，記得要先安裝 `Microsoft.EntityFrameworkCore.Design` 套件
    
    ```bash
    dotnet add package Microsoft.EntityFrameworkCore.Design
    ```
    
2.  透過 `dotnet ef migrations add` 建立一個 `Init` 資料庫移轉(Migration)就會出現問題
    
    ```bash
    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](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/08591d6e797f5dcead99-8053b01d-31c9-4716-a497-ea5796720644.webp)
    

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

1.  指定 `--startup-project` 或 `-s` 參數，指定**啟動專案**的資料夾路徑
    
    這個參數可以指定你要從哪個專案取得 DbContext 實例(Instance)，因為我們的 `WebApiEFCoreUnableCreateController` 專案是一個 ASP.NET Core 專案，所以這樣指定可以明確取得該專案所設定的**連接字串**與 `DbContextOptions<T>` 物件：
    
    ```bash
    dotnet ef migrations add init -s ..\WebApiEFCoreUnableCreateController
    ```
    
2.  改用 Visual Studio 2022 的 **Package Manager Console** (套件管理員主控台) 的 [EF Core tools (PowerShell)](https://learn.microsoft.com/en-us/ef/core/cli/powershell?WT.mc_id=DT-MVP-4015686)
    
    要使用 `Add-Migration` Cmdlet 命令，記得要先安裝 `Microsoft.EntityFrameworkCore.Tools` 套件
    
    ```bash
    dotnet add package Microsoft.EntityFrameworkCore.Tools
    ```
    
    在 **Package Manager Console** (套件管理員主控台) 執行以下命令
    
    ```powershell
    Add-Migration Init
    ```
    
    > 這個命令是可以順利建立 Migration 的！
    

### 解決方案

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

我們在執行 `dotnet ef migrations add init` 事實上有看到一個參考連結連到 [設計階段 DbContext 建立](https://go.microsoft.com/fwlink/?linkid=851728) 文件，裡面就有提到兩種可能的解決方案。

1.  使用不含參數的建構函式 (Using a constructor with no parameters)
    
    這個解決方案則是在 DbContext 加入一個**無參數的建構式**，以及 `override` 一個 [OnConfiguring](https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/?WT.mc_id=DT-MVP-4015686#simple-dbcontext-initialization-with-new) 方法即可。
    
    ```csharp
    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` 這樣的設計時期工具使用，其程式碼範例如下：
    
    ```csharp
    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，完全不會有錯誤發生！
    

### 相關連結

-   [DbContext Lifetime, Configuration, and Initialization](https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/?WT.mc_id=DT-MVP-4015686)
-   [Design-time DbContext Creation](https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?WT.mc_id=DT-MVP-4015686&tabs=dotnet-core-cli)
-   [EF Core NuGet Packages](https://learn.microsoft.com/en-us/ef/core/what-is-new/nuget-packages?WT.mc_id=DT-MVP-4015686)
-   [EF Core tools reference (Package Manager Console) - EF Core | Microsoft Learn](https://learn.microsoft.com/en-us/ef/core/cli/powershell?WT.mc_id=DT-MVP-4015686)
-   [Unable to execute "dotnet ef migrations add init" · Issue #31527 · dotnet/efcore](https://github.com/dotnet/efcore/issues/31527)
