DbContext 的存留期、設定與初始化
本文說明初始化與設定 DbContext 執行個體的基本模式。
警告
本文使用本機資料庫,其不需要使用者進行驗證。 實際執行應用程式應該使用可用的最安全驗證流程。 如需已部署測試與實際執行應用程式驗證的詳細資訊,請參閱安全驗證流程。
DbCoNtext 存留期
DbContext
的存留期始於建立執行個體時,於執行個體被處置時結束。 DbContext
實例的設計目的是要用於單一工作單位。 這表示 DbContext
執行個體的存留期通常很短。
提示
Martin Fowler 在上述連結中表示:「工作單位會持續追蹤您在商務交易期間所有影響資料庫的行為。 當您結束工作後,工作單位會找出因您的工作而必須完成的所有項目,以改變資料庫。」
使用 Entity Framework Core (EF Core) 時,一般工作單位會:
- 建立
DbContext
執行個體 - 依內容追蹤實體執行個體。 實體會追蹤者
- 視需要對追蹤的實體進行變更,以實作商務規則
- 已呼叫 SaveChanges 或 SaveChangesAsync。 EF Core 會偵測已執行的變更,並將變更寫入資料庫。
- 已處置
DbContext
執行個體
重要
- 請務必在使用後處置 DbContext 。 這可確保任何:
- Unmanaged 資源已釋出。
- 事件或其他勾點會取消註冊。 取消註冊可防止實例保持參考時記憶體流失。
- DbContext 不是 安全線程。 請勿在線程之間共享內容。 請務必先等候所有非同步呼叫,再繼續使用內容執行個體。
- EF Core 程式碼擲回的 InvalidOperationException 會使得內容成為無法復原的狀態。 這類例外狀況表示是因程式錯誤而無可復原。
ASP.NET Core 相依性插入中的 DbContext
在許多 Web 應用程式中,每個 HTTP 要求都會對應至單一工作單位。 這使得將內容存留期繫結至此類要求,成為非常好的 Web 應用程式預設行為。
ASP.NET Core 應用程式是使用相依性插入所設定。 EF Core 可以使用 在 中Program.cs
新增至此組態AddDbContext。 例如:
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string"
+ "'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
上述程式代碼會將 ApplicationDbContext
的子類別 DbContext
註冊為 ASP.NET Core 應用程式服務提供者中的範圍服務。 服務提供者也稱為相依性插入容器。 內容設定為使用 SQL Server 資料庫提供者,並從 ASP.NET Core 組態讀取 連接字串。
ApplicationDbContext
類別必須公開具有 DbContextOptions<ApplicationDbContext>
參數的公用建構函式。 這是內容設定從 AddDbContext
傳遞至 DbContext
的方式。 例如:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
ApplicationDbContext
可透過建構函式插入 ASP.NET 核心控制器或其他服務使用:
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}
最後的結果是為每個要求建立 ApplicationDbContext
執行個體並傳遞至控制器,在要求結束時,於處置前執行工作單位。
請進一步閱讀本文,以深入了解設定選項。 如需詳細資訊,請參閱 ASP.NET Core 中的相依性插入。
使用 'new' 進行基本 DbContext 初始化
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 Factory
有些應用程式類型 (例如 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
和相關的方法 - 在
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 處理變更追蹤 | 即將推出… |
注意
UseLazyLoadingProxies 和 UseChangeTrackingProxies 是來自 Microsoft.EntityFrameworkCore.Proxies NuGet 套件的擴充方法。 建議您使用這種 ".UseSomething()" 呼叫來設定及/或使用包含在其他套件中的 EF Core 延伸模組。
DbContextOptions
與 DbContextOptions<TContext>
接受的大多數DbContext
子類別DbContextOptions
都應該使用泛型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
註冊為暫時性,以達成此目的。