ASP.NET 7 Web API中如何隔离DbContext实例的保存操作?
问题根源
ASP.NET Core里默认DbContext的DI生命周期是请求级(Scoped),同一个请求里多次注入的MyDbContext其实是同一个实例——这就是为啥调用其中一个的SaveChangesAsync(),会把两个上下文的变更都提交的原因。
解决方案
1. 用DI实现完全独立的DbContext实例
方式一:用DbContext工厂(推荐)
先在Program.cs里注册DbContext工厂:
builder.Services.AddDbContextFactory<MyDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
然后在控制器里注入工厂,按需创建独立实例:
public class MyController : ControllerBase { private readonly IDbContextFactory<MyDbContext> _dbContextFactory; public MyController(IDbContextFactory<MyDbContext> dbContextFactory) { _dbContextFactory = dbContextFactory; } [HttpPost] public async Task<IActionResult> AddAsync(MyEntity myEntity) { // 预览用的临时上下文,用完自动释放,变更不会被保存 using var previewContext = _dbContextFactory.CreateDbContext(); previewContext.MyEntities.Add(myEntity); // 持久化用的上下文,单独保存自己的变更 using var saveContext = _dbContextFactory.CreateDbContext(); saveContext.MyEntities.Add(new MyEntity()); await saveContext.SaveChangesAsync(); return Ok(); } }
这种方式下两个上下文是完全独立的实例,各自的变更互不干扰。
方式二:注册独立的Scoped实例
在Program.cs里额外注册一个每次请求都生成新实例的DbContext:
// 常规持久化用的上下文 builder.Services.AddDbContext<MyDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // 预览用的上下文,每次请求都创建新实例 builder.Services.AddScoped<MyDbContext>(sp => { var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>(); optionsBuilder.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")); return new MyDbContext(optionsBuilder.Options); });
不过这种方式需要借助IServiceProvider手动区分获取,不如工厂模式直观。
2. 同一DbContext内区分实体组(不用多实例)
如果不想搞多个上下文,也可以在同一个DbContext里控制实体的跟踪状态:
- 预览用的实体:要么用
AsNoTracking()创建不被跟踪的实例,要么手动把实体状态设为Unchanged,这样保存时不会提交它的变更:
[HttpPost] public async Task<IActionResult> AddAsync(MyEntity myEntity) { // 预览实体:标记为不跟踪,修改后不会被保存 _dbContext.MyEntities.Attach(myEntity); _dbContext.Entry(myEntity).State = EntityState.Unchanged; // 要保存的实体 _dbContext.MyEntities.Add(new MyEntity()); await _dbContext.SaveChangesAsync(); // 只保存状态为Added的实体 return Ok(); }
关于事务的疑问
同一DbContext里的事务只能管理这个上下文内的所有变更,没法拆分不同实体组单独提交。如果用多个独立的DbContext,每个上下文可以自己开事务,互相不会影响。
内容的提问来源于stack exchange,提问作者Brian
相关产品推荐
相关产品推荐

