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