1. 简介
工作单元:维护受事务影响的对象列表,并协调对象改变的持久化和解决并发场景的问题
在 EntityFrameworkCore 中使用 DbContext 封装了:
实体对象状态记录跟踪
数据库的交互
数据库事务
关于协调对象改变的持久化是通过调用 DbContext 的相关方法实现的
在并发场景下 DbContext 的使用也完全交给了开发者处理,主要靠文档规范说明 DbContext 的使用。
2. DbContext 生命周期和使用规范 2.1. 生命周期DbContext 的生命周期从创建实例时开始,并在释放实例时结束。 DbContext 实例旨在用于单个工作单元。 这意味着 DbContext 实例的生命周期通常很短。
使用 Entity Framework Core (EF Core) 时的典型工作单元包括:
创建 DbContext 实例
根据上下文跟踪实体实例。 实体将在以下情况下被跟踪
查询返回时
添加或附加到 DbContext
根据需要对所跟踪的实体进行更改以实现业务规则
调用 SaveChanges 或 SaveChangesAsync
EF Core 将检测所做的更改,并将这些更改写入数据库。
释放 DbContext 实例
2.2. 使用规范使用后释放 DbContext 非常重要。 这可确保释放所有非托管资源,并注销任何事件或其他钩子(hooks),以防止实例在保持引用时出现内存泄漏。
DbContext 不是线程安全的。 不要在线程之间共享 DbContext。 请确保在继续使用 DbContext 实例之前,等待所有异步调用。
EF Core 代码引发的 InvalidOperationException 可以使 DbContext 进入不可恢复的状态。
此类异常指示程序错误,并且不应该从其中恢复。
2.3. 避免 DbContext 线程处理问题Entity Framework Core 不支持在同一 DbContext 实例上运行多个并行操作。
这包括异步查询的并行执行以及从多个线程进行的任何显式并发使用。
因此,始终 await 异步调用,或对并行执行的操作使用单独的 DbContext 实例。
当 EF Core 检测到尝试同时使用 DbContext 实例时,将会抛出异常 InvalidOperationException ,其中包含类:
在上一个操作完成之前,第二个操作已在此 DbContext 中启动。
使用同一个 DbContext 实例的不同线程不保证实例成员是线程安全的,因此抛出此异常。
如果框架没检测到并发访问,可能会导致不可预知的行为:应用程序崩溃、数据损坏等
并发访问 DbContext 实例的常见情况:
异步操作缺陷
使用异步方法,EF Core 可以启动以非阻塞式访问数据库的操作。 但是,如果调用方不等待其中一个方法完成,而是继续对 DbContext 执行其他操作,则 DbContext 的状态可能会(并且很可能会)损坏。
通过依赖注入隐式共享 DbContext 实例
默认情况下 AddDbContext 扩展方法使用有范围的生命周期来注册 DbContext 类型。
这样可以避免在大多数 ASP.NET Core 应用程序中出现并发访问问题
因为在给定时间内只有一个线程在执行每个客户端请求,并且每个请求都有单独的依赖注入范围(dependency injection scope)(因此有单独的 DbContext 实例)。
对于 Blazor Server 托管模型,一个逻辑请求用来维护 Blazor 用户线路,因此,如果使用默认注入范围,则每个用户线路只能提供一个范围内的 DbContext 实例。
建议
任何显式的并行执行多个线程的代码都应确保 DbContext 实例不会同时访问。
使用依赖注入可以通过以下方式实现:
将 DbContext 注册为范围的,并为每个线程创建范围的服务提供实例(使用 IServiceScopeFactory)
将 DbContext 注册为瞬时的(transient)
程序初始化时使用具有 ServiceLifetime 参数的 AddDbContext 方法的重载
引用:
3. 封装-工作单元上面的内容来源于官方文档,结合 使用规范 和 建议 的要求开始进行设计和封装。