DbContext 的存留期、設定與初始化

本文說明初始化與設定 DbContext 執行個體的基本模式。

DbCoNtext 存留期

DbContext 的存留期始於建立執行個體時,於執行個體被處置時結束。 DbContext 執行個體旨在供「單一」工作單位使用。 這表示 DbContext 執行個體的存留期通常很短。

提示

Martin Fowler 在上述連結中表示:「工作單位會持續追蹤您在商務交易期間所有影響資料庫的行為。 當您結束工作後,工作單位會找出因您的工作而必須完成的所有項目,以改變資料庫。」

使用 Entity Framework Core (EF Core) 時,一般工作單位會:

重要

  • 使用過後處置 DbContext 非常重要。 這可確保釋放所有不受管理的資源,以及所有未註冊的事件或其他勾點,以免在執行個體仍受參考時發生記憶體流失的狀況。
  • DbContext 不是安全執行緒。 請不要在執行緒之間共用內容。 請務必先等候所有非同步呼叫,再繼續使用內容執行個體。
  • EF Core 程式碼擲回的 InvalidOperationException 會使得內容成為無法復原的狀態。 這類例外狀況表示是因程式錯誤而無可復原。

ASP.NET Core 相依性插入中的 DbContext

在許多 Web 應用程式中,每個 HTTP 要求都會對應至單一工作單位。 這使得將內容存留期繫結至此類要求,成為非常好的 Web 應用程式預設行為。

ASP.NET Core 應用程式是使用相依性插入所設定。 您可以在 Startup.csConfigureServices 方法中,使用 AddDbContext 將 EF Core 新增至此設定。 例如:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddDbContext<ApplicationDbContext>(
        options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection"));
}

本範例會在 ASP.NET Core 應用程式服務提供者 (又名相依性插入容器) 中,將名為 ApplicationDbContextDbContext 子類別註冊為範圍服務。 此內容設定為使用 SQL Server 資料庫提供者,會從 ASP.NET Core 設定中讀取連接字串。 在 ConfigureServices 的什麼「位置」呼叫 AddDbContext 不重要。

ApplicationDbContext 類別必須公開具有 DbContextOptions<ApplicationDbContext> 參數的公用建構函式。 這是內容設定從 AddDbContext 傳遞至 DbContext 的方式。 例如:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

然後,您就可以透過建構函式插入,在 ASP.NET Core 控制器或其他服務中使用 ApplicationDbContext。 例如:

public class MyController
{
    private readonly ApplicationDbContext _context;

    public MyController(ApplicationDbContext context)
    {
        _context = context;
    }
}

最後的結果是為每個要求建立 ApplicationDbContext 執行個體並傳遞至控制器,在要求結束時,於處置前執行工作單位。

請進一步閱讀本文,以深入了解設定選項。 此外,如需 ASP.NET Core 設定和相依性插入的詳細資訊,亦請參閱 ASP.NET Core 中的應用程式啟動ASP.NET Core 中的相依性插入

使用 'new' 的簡易 DbCoNtext 初始化

您可使用一般的 .NET 方法建構 DbContext 執行個體,例如在 C# 中使用 new。 覆寫 OnConfiguring 方法或將選項傳遞至建構函式,即可執行設定。 例如:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
    }
}

此模式也方便透過 DbContext 建構函式傳遞設定,例如連接字串。 例如:

public class ApplicationDbContext : DbContext
{
    private readonly string _connectionString;

    public ApplicationDbContext(string connectionString)
    {
        _connectionString = connectionString;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(_connectionString);
    }
}

或者,您也可以使用 DbContextOptionsBuilder 建立接著會傳遞至 DbContext 建構函式的 DbContextOptions 物件。 這可以明確建構針對相依性插入設定的 DbContext。 例如,使用上述針對 ASP.NET Core Web 應用程式定義的 ApplicationDbContext 時:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

即會建立 DbContextOptions,並可明確呼叫建構函式:

var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
    .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0")
    .Options;

using var context = new ApplicationDbContext(contextOptions);

使用 DbCoNtext 處理站 (例如用於 Blazor)

有些應用程式類型 (例如 ASP.NET Core Blazor) 使用相依性插入,但不會建立符合所需 DbContext 存留期的服務範圍。 即使有這種服務範圍,應用程式還是需要在此範圍內執行多個工作單位。 例如,單一 HTTP 要求內的多個工作單位。

在這些情況下,您可以使用 AddDbContextFactory 註冊處理站,以建立 DbContext 執行個體。 例如:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContextFactory<ApplicationDbContext>(
        options =>
            options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"));
}

ApplicationDbContext 類別必須公開具有 DbContextOptions<ApplicationDbContext> 參數的公用建構函式。 這與上節傳統 ASP.NET Core 所用的模式相同。

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

然後,您就可以透過建構函式插入,在其他服務中使用 DbContextFactory 處理站。 例如:

private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;

public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
    _contextFactory = contextFactory;
}

接著,在服務程式碼中使用插入的處理站建構 DbCoNtext 執行個體。 例如:

public void DoSomething()
{
    using (var context = _contextFactory.CreateDbContext())
    {
        // ...
    }
}

請注意,以此方式建立的 DbContext 執行個體「不受」應用程式服務提供者管理,因此必須由應用程式處置。

如需使用 EF Core 搭配 Blazor 的詳細資訊,請參閱 使用 Entity Framework Core 的 ASP.NET Core Blazor 的 Blazor Server

DbContextOptions

所有 DbContext 設定的起點皆為 DbContextOptionsBuilder。 有三種方式可取得此產生器:

  • 使用 AddDbContext 和相關的方法
  • In OnConfiguring
  • 使用 new 明確建構

上述各節已顯示各個範例。 不論產生器的來源為何,都可以套用相同的設定。 此外,不論內容建構方式為何,一律會呼叫 OnConfiguring。 這表示,即使在使用 AddDbContext 時,也可以使用 OnConfiguring 執行其他設定。

設定資料庫提供者

每個 DbContext 執行個體都必須設定為只能使用一個資料庫提供者。 (DbContext 子類型的不同執行個體可以搭配不同的資料庫提供者使用,但一個執行個體只能使用一個資料庫提供者。) 資料庫提供者是使用特定的 Use* 呼叫所設定。 例如,若要使用 SQL Server 資料庫提供者:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
    }
}

這些 Use* 方法都是資料庫提供者實作的擴充方法。 這表示您必須先安裝資料庫提供者 NuGet 套件,才能使用擴充方法。

提示

EF Core 資料庫提供者會廣泛使用擴充方法。 如果編譯器指出找不到某個方法,請確定已安裝提供者的 NuGet 套件,且程式碼中有 using Microsoft.EntityFrameworkCore;

下表包含常見資料庫提供者範例。

資料庫系統 範例設定 NuGet 套件
SQL Server 或 Azure SQL .UseSqlServer(connectionString) Microsoft.EntityFrameworkCore.SqlServer
Azure Cosmos DB .UseCosmos(connectionString, databaseName) Microsoft.EntityFrameworkCore.Cosmos
SQLite .UseSqlite(connectionString) Microsoft.EntityFrameworkCore.Sqlite
EF Core 記憶體內部資料庫 .UseInMemoryDatabase(databaseName) Microsoft.EntityFrameworkCore.InMemory
PostgreSQL* .UseNpgsql(connectionString) Npgsql.EntityFrameworkCore.PostgreSQL
MySQL/MariaDB* .UseMySql(connectionString) Pomelo.EntityFrameworkCore.MySql
甲骨文* .UseOracle(connectionString) Oracle.EntityFrameworkCore

*這些資料庫提供者非由 Microsoft 出貨。 如需資料庫提供者的詳細資訊,請參閱資料庫提供者

警告

EF Core 記憶體內部資料庫並非專為生產環境使用所設計。 而且,可能不是用於測試的最佳選擇。 如需詳細資訊,請參閱測試使用 EF Core 的程式碼

如需使用連接字串與 EF Core 的詳細資訊,請參閱連接字串

資料庫提供者特定的選用設定是在其他提供者特定的產生器中執行。 例如,使用 EnableRetryOnFailure 設定連線至Azure SQL 時的恢復連線重試次數:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=Test",
                providerOptions => { providerOptions.EnableRetryOnFailure(); });
    }
}

提示

在 SQL Server 和 Azure SQL 中使用相同的資料庫提供者。 不過,連線至 SQL Azure 時,還是建議使用恢復連線

如需提供者特定設定的詳細資訊,請參閱資料庫提供者

其他 DbCoNtext 設定

其他 DbContext 設定可以在呼叫 Use* 之前或之後鏈結 (沒有任何差異)。 例如,開啟敏感性資料記錄:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .EnableSensitiveDataLogging()
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
    }
}

下表包含常在 DbContextOptionsBuilder 中呼叫的方法範例。

DbContextOptionsBuilder 方法 作用 深入了解
UseQueryTrackingBehavior 設定查詢的預設追蹤行為 查詢追蹤行為
LogTo 取得 EF Core 記錄的簡單方式 記錄、事件與診斷
UseLoggerFactory 註冊 Microsoft.Extensions.Logging 處理站 記錄、事件與診斷
EnableSensitiveDataLogging 包括例外狀況和記錄中的應用程式資料 記錄、事件與診斷
EnableDetailedErrors 更詳細的查詢錯誤 (代價是效能) 記錄、事件與診斷
ConfigureWarnings 忽略或擲回警告和其他事件 記錄、事件與診斷
AddInterceptors 註冊 EF Core 攔截器 記錄、事件與診斷
UseLazyLoadingProxies 使用動態 Proxy 處理消極式載入 消極式載入
UseChangeTrackingProxies 使用動態 Proxy 處理變更追蹤 即將推出…

注意

UseLazyLoadingProxiesUseChangeTrackingProxies 是來自 Microsoft.EntityFrameworkCore.Proxies NuGet 套件的擴充方法。 建議您使用這種 ".UseSomething()" 呼叫來設定及/或使用包含在其他套件中的 EF Core 延伸模組。

DbContextOptionsDbContextOptions<TContext>

大部分接受 DbContextOptionsDbContext 子類別都應該使用泛型DbContextOptions<TContext>變化。 例如:

public sealed class SealedApplicationDbContext : DbContext
{
    public SealedApplicationDbContext(DbContextOptions<SealedApplicationDbContext> contextOptions)
        : base(contextOptions)
    {
    }
}

這可確保即使已註冊多個 DbContext 子類型,也能正確選取從相依性插入解析的特定 DbContext 子類型。

提示

您不需要密封 DbCoNtext,但對於未設計成可供繼承的類別而言,最好還是密封。

不過,如果 DbContext 子類型本身就是可供繼承的,則應該公開採用非泛型 DbContextOptions 的受保護建構函式。 例如:

public abstract class ApplicationDbContextBase : DbContext
{
    protected ApplicationDbContextBase(DbContextOptions contextOptions)
        : base(contextOptions)
    {
    }
}

這可讓多個實體子類別使用其不同的泛型 DbContextOptions<TContext> 執行個體來呼叫這個基底建構函式。 例如:

public sealed class ApplicationDbContext1 : ApplicationDbContextBase
{
    public ApplicationDbContext1(DbContextOptions<ApplicationDbContext1> contextOptions)
        : base(contextOptions)
    {
    }
}

public sealed class ApplicationDbContext2 : ApplicationDbContextBase
{
    public ApplicationDbContext2(DbContextOptions<ApplicationDbContext2> contextOptions)
        : base(contextOptions)
    {
    }
}

請注意,這與直接繼承自 DbContext 時所用的模式完全相同。 也就是說,DbContext 建構函式本身會基於這個原因接受非泛型 DbContextOptions

兼具可具現化及可供繼承的 DbContext 子類別應該公開這兩種建構函式格式。 例如:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> contextOptions)
        : base(contextOptions)
    {
    }

    protected ApplicationDbContext(DbContextOptions contextOptions)
        : base(contextOptions)
    {
    }
}

設計階段的 DbCoNtext 設定

EF Core 設計階段工具,例如適用於 EF Core 移轉的工具,必須能夠探索及建立 DbContext 類型的工作執行個體,以收集有關應用程式實體類型的詳細資料,及其對應至資料庫結構描述的方式。 只要工具可以使用與執行階段設定方式類似的設定方式,輕鬆建立 DbContext,此程序即可自動化。

雖然向 DbContext 提供必要設定資訊的所有模式都可以在執行階段運作,但要求在設計階段使用 DbContext 時的工具,可使用的模式有所限制。 詳細說明請參閱設計階段內容建立

避免 DbCoNtext 執行緒問題

Entity Framework Core 不支援在同一 DbContext 執行個體上執行多個平行作業。 這包括平行執行非同步查詢,以及明確同時使用多個執行緒。 因此,請一律立即 await 非同步呼叫,或針對平行執行的作業使用不同的 DbContext 執行個體。

當 EF Core 偵測到同時使用 DbContext 執行個體的嘗試時,您會看到 InvalidOperationException,並顯示如下訊息:

第二個作業未待上一個作業完成,即於此內容中啟動。 這通常是因為有不同的執行緒使用相同的 DbCoNtext 執行個體所造成,但執行個體成員不保證為安全執行緒。

如果未偵測到同時存取,可能會造成未定義的行為、應用程式損毀和資料損毀。

有些常見錯誤會意外造成同時存取相同的 DbContext 執行個體:

非同步作業錯誤

非同步方法可讓 EF Core 以非封鎖方式起始資料庫存取作業。 但若呼叫端不等候其中一個方法完成,就繼續在 DbContext 中執行其他作業,則 DbContext 的狀態就可能 (且極其可能) 會損毀。

一律立即等候 EF Core 非同步方法。

透過相依性插入隱含共用 DbCoNtext 執行個體

AddDbContext 擴充方法預設會註冊具有限定範圍存留期DbContext 類型。

這在大部分的 ASP.NET Core 應用程式同時存取問題中算是安全的,因為只有一個執行緒在指定的時間執行每個用戶端的要求,而且每個要求都會得到不同的相依性插入範圍 (因此是不同的 DbContext 執行個體)。 若是 Blazor Server 主控模型,其會使用一個邏輯要求來維護 Blazor 使用者電路,因此如果使用預設的插入範圍,則每個使用者電路只能使用一個限定範圍的 DbCoNtext 執行個體。

任何明確平行執行多個執行緒的程式碼,都應該確保 DbContext 執行個體不被同時存取。

使用相依性插入,即可將內容註冊為限定範圍,並 (使用 IServiceScopeFactory) 建立每個執行緒的範圍,或 (使用接受 ServiceLifetime 參數的 AddDbContext 多載) 將 DbContext 註冊為暫時性,以達成此目的。

閱讀更多內容