002從零開始入門Entity Framework Core——DbContext生存期、配置和初始化
閱讀須知:本文為入門介紹、指引文章,所示代碼皆為最簡易(或僅為實現功能)的演示示例版本,不一定切實符合個人(企業)實際開發需求。
一、DbContext生存期
DbContext 的生存期從創建實例時開始,並在釋放實例時結束。 DbContext 實例旨在用於單個工作單元。這意味着 DbContext 實例的生存期通常很短。
使用 Entity Framework Core (EF Core) 時的典型工作單元包括:
- 創建 DbContext 實例
- 根據上下文跟蹤實體實例。 實體將在以下情況下被跟蹤
- 正在從查詢返回
- 正在添加或附加到上下文
- 根據需要對所跟蹤的實體進行更改以實現業務規則
- 調用 SaveChanges 或 SaveChangesAsync。 EF Core 檢測所做的更改,並將這些更改寫入數據庫
- 釋放 DbContext 實例
注意事項:
1、使用後釋放 DbContext 非常重要。這可確保釋放所有非託管資源,並註銷任何事件或其他掛鈎,以防止在實例保持引用時出現內存泄漏。
2、DbContext 不是線程安全的。不要在線程之間共享上下文。請確保在繼續使用上下文實例之前,等待所有異步調用。
3、EF Core 代碼引發的 InvalidOperationException 可以使上下文進入不可恢復的狀態。此類異常指示程序錯誤,並且不旨在從其中恢復。
二、ASP.NET Core 依賴關係注入中的 DbContext
在許多 Web 應用程序中,每個 HTTP 請求都對應於單個工作單元。這使得上下文生存期與請求的生存期相關,成為 Web 應用程序的一個良好默認值。
使用依賴關係注入配置 ASP.NET Core 應用程序。可以在 Startup.cs
文件的 ConfigureServices
方法中,用 AddDbContext 擴展方法將 EF Core 添加到此處進行配置。
本文中我使用的是 MySQL 數據庫,例如:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
#region 配置MySQL數據庫
var connectionString = "server=數據庫部署的服務器地址;user=數據庫登錄用戶名;password=數據庫登錄密碼;database=數據庫名;charset=utf8";
var serverVersion = new MySqlServerVersion(new Version(5, 7, 22));
services.AddDbContext<CustomAppDbContext>(
dbContextOptions => dbContextOptions
.UseMySql(connectionString, serverVersion)
);
#endregion
}
此示例將名為 CustomAppDbContext 的 DbContext 子類註冊為 ASP.NET Core 應用程序服務提供程序(也稱為依賴關係注入容器)中的作用域服務。上下文配置為使用 MySQL 數據庫提供程序,並將從 ASP.NET Core 配置讀取連接字符串。在 ConfigureServices 中的何處調用 AddDbContext 通常不重要。
F12轉到 DbContext 類的定義,發現其有參構造函數定義形式為:
public DbContext([NotNullAttribute] DbContextOptions options);
CustomAppDbContext 類必須公開具有 DbContextOptions<CustomAppDbContext> 參數的公共構造函數。這是將 AddDbContext 的上下文配置傳遞到 DbContext 的方式。例如:
public class CustomAppDbContext : DbContext
{
public CustomAppDbContext(DbContextOptions<CustomAppDbContext> options) : base(options)//調用父類的構造函數
{
}
public DbSet<Student> Student { get; set; }
}
其中 Student 類如下所示:
public partial class Student
{
public string Id { get; set; }
public string Name { get; set; }
public DateTime? JoinTime { get; set; }
public int Sex { get; set; }
}
然後,CustomAppDbContext 可以通過構造函數注入在 ASP.NET Core 控制器或其他服務中使用。例如:
public class MyController : Controller
{
private readonly CustomAppDbContext _context;
public MyController(CustomAppDbContext context)//構造函數
{
_context = context;
}
public JsonResult Index()
{
_context.Student.Add(new Student
{
Id = "10001",
Name = "張三",
JoinTime = DateTime.Now,
Sex = 1
});
_context.SaveChanges();
return new JsonResult("success");
}
}
最終結果是為每個請求創建一個 CustomAppDbContext 實例,並傳遞給控制器,以在請求結束後釋放前執行工作單元。
三、使用「new」的簡單的 DbContext 初始化
可以按照常規的 .NET 方式構造 DbContext 實例,例如,使用 C# 中的 new
。可以通過重寫 OnConfiguring 方法或通過將選項傳遞給構造函數來執行配置。
1、重寫 OnConfiguring 方法。
F12轉到 DbContext 類的定義,發現 OnConfiguring 方法的定義形式為:
protected internal virtual void OnConfiguring(DbContextOptionsBuilder optionsBuilder);
DbContext 子類的代碼示例如下所示:
public class NewCustomAppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql("server=數據庫部署的服務器地址;user=數據庫登錄用戶名;password=數據庫登錄密碼;database=數據庫名;charset=utf8", new MySqlServerVersion(new Version(5, 7, 22)));
}
public DbSet<Student> Student { get; set; }
}
此種方式構造的 DbContext 實例在控制器方法中調用如下所示:
public class MyNewController : Controller
{
public string Index()
{
using var db = new NewCustomAppDbContext();
var list = db.Student.ToList();
return JsonConvert.SerializeObject(list);
}
}
2、通過 DbContext 構造函數傳遞配置
通過此模式,我們還可以輕鬆地通過 DbContext 構造函數傳遞配置(如連接字符串)。例如:
public class NewCustomAppDbContext : DbContext
{
private readonly string _connectionString;
public NewCustomAppDbContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql(_connectionString, new MySqlServerVersion(new Version(5, 7, 22)));
}
public DbSet<Student> Student { get; set; }
}
此種方式構造的 DbContext 實例在控制器方法中調用如下所示:
public class MyNewController : Controller
{
public string Index()
{
using var db = new NewCustomAppDbContext("server=數據庫部署的服務器地址;user=數據庫登錄用戶名;password=數據庫登錄密碼;database=數據庫名;charset=utf8");
var list = db.Student.ToList();
return JsonConvert.SerializeObject(list);
}
}
3、使用 DbContextOptionsBuilder 創建 DbContextOptions 對象
可以使用 DbContextOptionsBuilder 創建 DbContextOptions 對象,然後將該對象傳遞到 DbContext 構造函數。這使得為依賴關係注入配置的 DbContext 也能顯式構造。例如:
public class DICustomAppDbContext:DbContext
{
public DICustomAppDbContext(DbContextOptions<DICustomAppDbContext> optionsBuilder):base(optionsBuilder)
{
}
public DbSet<Student> Student { get; set; }
}
此種構造方式,在 Controller 中可以創建 DbContextOptions,並可以顯式調用構造函數,代碼如下所示:
public class MyDIController : Controller
{
private readonly string _connectionString = "server=數據庫部署的服務器地址;user=數據庫登錄用戶名;password=數據庫登錄密碼;database=數據庫名;charset=utf8";
private readonly MySqlServerVersion _serverVersion = new MySqlServerVersion(new Version(5, 7, 22));
public string Index()
{
var contextOptions = new DbContextOptionsBuilder<DICustomAppDbContext>()
.UseMySql(_connectionString, _serverVersion)
.Options;
using var context = new DICustomAppDbContext(contextOptions);
var list = context.Student.ToList();
return JsonConvert.SerializeObject(list);
}
}
四、使用 DbContext 工廠
某些應用程序類型(例如 ASP.NET Core Blazor)使用依賴關係注入,但不創建與所需的 DbContext 生存期一致的服務作用域。即使存在這樣的對齊方式,應用程序也可能需要在此作用域內執行多個工作單元。例如,單個 HTTP 請求中的多個工作單元。
在這些情況下,可以使用 AddDbContextFactory 來註冊工廠以創建 DbContext 實例。例如:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
#region 配置MySQL數據庫
var connectionString = "server=數據庫部署的服務器地址;user=數據庫登錄用戶名;password=數據庫登錄密碼;database=數據庫名;charset=utf8";
var serverVersion = new MySqlServerVersion(new Version(5, 7, 22));
services.AddDbContextFactory<FactoryCustomAppDbContext>(
dbContextOptions => dbContextOptions
.UseMySql(connectionString, serverVersion)
);
#endregion
}
FactoryCustomAppDbContext 類必須公開具有 DbContextOptions<FactoryCustomAppDbContext> 參數的公共構造函數。此模式與上面傳統 ASP.NET Core 部分中使用的模式相同。例如:
public class FactoryCustomAppDbContext : DbContext
{
public FactoryCustomAppDbContext(DbContextOptions<FactoryCustomAppDbContext> options) : base(options)
{
}
public DbSet<Student> Student { get; set; }
}
然後,可以通過構造函數注入在其他服務中使用 DbContextFactory 工廠。最後,可以使用注入的工廠在服務代碼中構造 DbContext 實例。例如:
public class MyFactoryController : Controller
{
private readonly IDbContextFactory<FactoryCustomAppDbContext> _contextFactory;
public MyFactoryController(IDbContextFactory<FactoryCustomAppDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public string Index()
{
using (var context = _contextFactory.CreateDbContext())
{
var list = context.Student.ToList();
return JsonConvert.SerializeObject(list);
}
}
}
請注意,以這種方式創建的 DbContext 實例並非由應用程序的服務提供程序進行管理,因此必須由應用程序釋放。
五、DbContextOptions
所有 DbContext 配置的起始點都是 DbContextOptionsBuilder。可以通過以下三種方式獲取此生成器:
- 在 AddDbContext 和相關方法中
- 在 OnConfiguring 中
- 使用 new 顯式構造
每種配置方式的示例在本文上述內容中都進行了講解和代碼展示。無論生成器來自何處,都可以應用相同的配置。此外,無論如何構造上下文,都將始終調用 OnConfiguring。這意味着即使使用 AddDbContext,OnConfiguring 也可用於執行其他配置。
六、配置數據庫提供程序
每個 DbContext 實例都必須配置為使用一個且僅一個數據庫提供程序。(DbContext 子類型的不同實例可用於不同的數據庫提供程序,但一個實例只能使用一個。)一個數據庫提供程序要使用一個特定的 Use*
調用進行配置。
例如,若要使用 MySQL 數據庫提供程序:
public class MySQLAppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql("數據庫連接字符串", new MySqlServerVersion(new Version(5, 7, 22)));
}
}
例如,若要使用 SQL Server 數據庫提供程序:
public class SQLServerApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("數據庫連接字符串");
}
}
這些 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 |
Oracle | .UseOracle(connectionString) | Oracle.EntityFrameworkCore |
六、避免 DbContext 線程處理問題
Entity Framework Core 不支持在同一 DbContext 實例上運行多個並行操作。這包括異步查詢的並行執行以及從多個線程進行的任何顯式並發使用。因此,始終立即 await
異步調用,或對並行執行的操作使用單獨的 DbContext 實例。
當 EF Core 檢測到嘗試同時使用 DbContext 實例的情況時,你將看到 InvalidOperationException
,其中包含類似於以下內容的消息:
A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe.
翻譯成中文就是:
在上一個操作完成之前,在此上下文上啟動了第二個操作。這通常是由不同線程使用相同的DbContext實例引起的,但不保證實例成員是線程安全的。
當並發訪問未被檢測到時,可能會導致未定義的行為、應用程序崩潰和數據損壞。
七、異步操作缺陷
使用異步方法,EF Core 可以啟動以非阻擋式訪問數據庫的操作。但是,如果調用方不等待其中一個方法完成,而是繼續對 DbContext 執行其他操作,則 DbContext 的狀態可能會(並且很可能會)損壞。
應始終立即等待 EF Core 異步方法。
——————————-本篇文章到此結束————————————-