DbContext生存期
DbContext的生存期從創建實例開始,並在釋放實例時結束。DbContext實例旨在用於單個工作單元。這意味着DbContext實例的生存期通常很短。
工作單元:維護受業務交易影響的對象的列表,並協調更改的注銷和並發問題的解決。
在將數據移入和移出數據庫時,重要的是要跟蹤所做的更改。否則,該數據將不會被寫回到數據庫中。同樣,您必須插入創建的新對象並刪除所有刪除的對象。
您可以隨着對象模型的每次更改來更改數據庫,但是這可能導致許多非常小的數據庫調用,這最終會非常緩慢。此外,它要求您為整個交互打開一個事務,如果您的業務事務跨越多個請求,則這是不切實際的。如果您需要跟蹤已讀取的對象,從而避免不一致的讀取,則情況甚至更糟。
工作單元會跟蹤您在業務交易過程中可能影響數據庫的所有操作。完成后,它會計算出由於工作而需要更改數據庫的所有工作。英文原文請參考:EAA的P。
EF Core的典型工作單元包括(重點理解這段,有助於我們對EF Core工作原理的理解):
- 創建DbContext實例
- 根據上下文跟蹤實體實例。實體將在以下情況下被追蹤
- 正在從查詢返回
- 正在添加或附加到上下文
- 根據需要對所跟蹤的實體進行更改以實現業務規則
- 調用SaveChanges或SaveChangesAsync。EF Core檢測所做的更改,並將這些更改寫入到數據庫
- 釋放DbContext
重要知識點:
- 使用后釋放DbContext非常重要。這可確保釋放所有非托管資源,並注銷任何事件或其他掛鈎,以防止在實例保持引用時出現內存泄漏。
- DbContext不是線程安全的。不要在線程間共享上下文。請確保在繼續使用上下文實例之前,等待所有異步調用。
- EF Core引發的InvalidOperationException可以使上下文進入不可恢復狀態。
ASP.NET Core 依賴關系注入中的 DbContext
在許多 Web 應用程序中,每個 HTTP 請求都對應於單個工作單元。 這使得上下文生存期與請求的生存期相關。
使用依賴關系注入配置 ASP.NET Core 應用程序。 可以使用 Startup.cs
的 ConfigurureServices
方法中的 AddDbContext 將 EF Core 添加到此配置。 例如:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddDbContext<ApplicationDbContext>( options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection")); }
此示例將名為 ApplicationDbContext
的 DbContext
子類注冊為 ASP.NET Core 應用程序服務提供程序(也稱為 依賴關系注入容器)中的作用域服務。
options => options.UseSqlServer上下文配置為使用 SQL Server 數據庫提供程序,
"name=ConnectionStrings:DefaultConnection"表示將從 ASP.NET Core 配置讀取連接字符串。 在 ConfigureServices
中的何處調用 AddDbContext
通常不重要。
ApplicationDbContext
(數據庫上下文)類必須公開具有 DbContextOptions<ApplicationDbContext>
參數的公共構造函數。 這是將 AddDbContext
的上下文配置傳遞到 DbContext
的方式。 例如:
public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
然后,ApplicationDbContext
可以通過構造函數注入在 ASP.NET Core 控制器或其他服務中使用。 例如:
1 public class MyController 2 { 3 private readonly ApplicationDbContext _context; 4 5 public MyController(ApplicationDbContext context) 6 { 7 _context = context; 8 } 9 }
最終結果是為每個請求創建一個 ApplicationDbContext
實例,並傳遞給控制器,以在請求結束后釋放前執行工作單元。
使用“new”的簡單的 DbContext 初始化
可以按照常規的 .NET 方式構造 DbContext
實例,例如,使用 C# 中的 new
。 可以通過重寫 OnConfiguring
方法或通過將選項傳遞給構造函數來執行配置。 例如:
1 public class ApplicationDbContext : DbContext 2 { 3 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 4 { 5 optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"); 6 } 7 }
通過此模式,還可以輕松地通過 DbContext
構造函數傳遞配置(如連接字符串)。 例如:
1 public class ApplicationDbContext : DbContext 2 { 3 private readonly string _connectionString; 4 5 public ApplicationDbContext(string connectionString) 6 { 7 _connectionString = connectionString; 8 } 9 10 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 11 { 12 optionsBuilder.UseSqlServer(_connectionString); 13 } 14 }
或者,可以使用 DbContextOptionsBuilder
創建 DbContextOptions
對象,然后將該對象傳遞到 DbContext
構造函數。 這使得為依賴關系注入配置的 DbContext
也能顯式構造。 例如,使用上述為 ASP.NET Core 的 Web 應用定義的 ApplicationDbContext
時:
1 public class ApplicationDbContext : DbContext 2 { 3 public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 4 : base(options) 5 { 6 } 7 }
可以創建 DbContextOptions
,並可以顯式調用構造函數:
var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>() .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test") .Options; using var context = new ApplicationDbContext(contextOptions);
使用 DbContext 工廠(例如對於 Blazor)
某些應用程序類型(例如 ASP.NET Core Blazor)使用依賴關系注入,但不創建與所需的 DbContext
生存期一致的服務作用域。 即使存在這樣的對齊方式,應用程序也可能需要在此作用域內執行多個工作單元。 例如,單個 HTTP 請求中的多個工作單元。在這些情況下,可以使用 AddDbContextFactory 來注冊工廠以創建 DbContext
實例。 例如:
1 public void ConfigureServices(IServiceCollection services) 2 { 3 services.AddDbContextFactory<ApplicationDbContext>(options => 4 options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test")); 5 }
ApplicationDbContext
類必須公開具有 DbContextOptions<ApplicationDbContext>
參數的公共構造函數。 此模式與上面傳統 ASP.NET Core 部分中使用的模式相同。
1 public class ApplicationDbContext : DbContext 2 { 3 public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 4 : base(options) 5 { 6 } 7 }
然后,可以通過構造函數注入在其他服務中使用 DbContextFactory
工廠。 例如:
1 private readonly IDbContextFactory<ApplicationDbContext> _contextFactory; 2 3 public MyController(IDbContextFactory<ApplicationDbContext> contextFactory) 4 { 5 _contextFactory = contextFactory; 6 }
然后,可以使用注入的工廠在服務代碼中構造 DbContext 實例。 例如:
1 public void DoSomething() 2 { 3 using (var context = _contextFactory.CreateDbContext()) 4 { 5 // ... 6 } 7 }
請注意,以這種方式創建的 DbContext
實例並非由應用程序的服務提供程序進行管理,因此必須由應用程序釋放。
DbContextOptions
所有 DbContext
配置的起始點都是 DbContextOptionsBuilder。 可以通過三種方式獲取此生成器:
- 在
AddDbContext
和相關方法中 - 在
OnConfiguring
中 - 使用
new
顯式構造
上述各節顯示了其中每個示例。 無論生成器來自何處,都可以應用相同的配置。 此外,無論如何構造上下文,都將始終調用 OnConfiguring
。 這意味着即使使用 AddDbContext
,OnConfiguring
也可用於執行其他配置。
配置數據庫提供程序
每個 DbContext
實例都必須配置為使用一個且僅一個數據庫提供程序。 (DbContext
子類型的不同實例可用於不同的數據庫提供程序,但單個實例只能使用一個。)使用特定的 Use*
" 調用配置數據庫提供程序。 例如,若要使用 SQL Server 數據庫提供程序:
1 public class ApplicationDbContext : DbContext 2 { 3 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 4 { 5 optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"); 6 } 7 }
這些 Use*
" 方法是由數據庫提供程序實現的擴展方法。 這意味着必須先安裝數據庫提供程序 NuGet 包,然后才能使用擴展方法。
EF Core 數據庫提供程序廣泛使用擴展方法。 如果編譯器指示找不到方法,請確保已安裝提供程序的 NuGet 包,並且你在代碼中已有 using Microsoft.EntityFrameworkCore;
。
下表包含常見數據庫提供程序的示例:
|
|
|
|||
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 | |||
Oracle* | .UseOracle(connectionString) | Oracle.EntityFrameworkCore |
避免 DbContext 線程處理問題
Entity Framework Core 不支持在同一 DbContext
實例上運行多個並行操作。 這包括異步查詢的並行執行以及從多個線程進行的任何顯式並發使用。 因此,始終立即 await
異步調用,或對並行執行的操作使用單獨的 DbContext
實例。
當 EF Core 檢測到嘗試同時使用 DbContext
實例的情況時,你將看到 InvalidOperationException