Entity Framework Core - DbContext配置和初始化
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 新增到此配置。例如:
publicvoid 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