EF 6.0 多仓储DbContext注入下SaveChanges无法保存订单问题
问题现象
若先使用注入到CustomersRepository的DbContext执行操作,再通过OrdersRepository新增Order,Order的变更无法保存到数据库。
环境配置
技术栈为最新版C#、.NET 6.0、EF 6.0,本地运行SQL Server,采用Clean Architecture变体架构。项目包含CustomersRepository、OrdersRepository两个独立仓储,二者均注入DbContext实例,所有依赖注入生命周期配置为Scoped:
services.AddScoped<ICustomersRepository, CustomersRepository>();
问题复现代码
// 以下第一行代码为问题触发点 var currentCustomer = await customersRepository.GetCustomer(id); // 删除该行后,调用CreateOrder()即可正常保存数据到数据库,这是为什么? var newOrder = new Order() { CustomerId = currentCustomer.Id }; var orderId = await ordersRepository.CreateOrder(newOrder); // 此处未将数据保存到数据库,也未抛出任何错误 await customersRepository.SaveCustomer(currentCustomer); // 此处才会将newOrder保存到数据库,若newOrder不符合校验规则则在此处报错
CustomersRepository实现代码
/// <summary> /// 构造函数 /// </summary> /// <param name="dbContext"></param> public CustomersRepository(ApplicationDbContext dbContext) { this.dbContext = dbContext; } public async Task<int> SaveCustomer(Customer customer) { dbContext.Customers.Update(customer); await dbContext.SaveChangesAsync(); return customer.Id; }
OrdersRepository实现代码
/// <summary> /// 构造函数 /// </summary> /// <param name="dbContext"></param> public OrdersRepository(ApplicationDbContext dbContext) { this.dbContext = dbContext; } public async Task<int> CreateOrder(Order order) { await dbContext.Orders.AddAsync(order); await dbContext.SaveChangesAsync(); return order.Id; }
问题根因
这个问题是EF Core变更追踪机制、隐式关系修复和代码实现错误共同导致的:
- 调用
GetCustomer查询Customer后,如果没有开启AsNoTracking,Customer实体会被当前请求内共享的Scoped DbContext追踪。 - 创建newOrder时仅赋值了
CustomerId外键,没有显式设置Order.Customer导航属性,也没有将Order加入Customer.Orders导航集合。此时调用AddAsync标记Order为Added状态时,EF Core的隐式关系修复逻辑会自动将Order关联到已追踪的Customer实体上,但因为没有显式声明关联状态,第一次调用SaveChangesAsync时,EF Core不会将这个隐式关联的Order识别为需要提交的新增实体,不会生成INSERT语句。 - 后续调用
SaveCustomer时,代码里手动执行了dbContext.Customers.Update(customer),这个方法会强制遍历Customer实体所有可达的关联实体(包括刚才隐式关联上的newOrder),将所有未追踪/状态不明确的关联实体全部纳入上下文追踪,将newOrder正式标记为Added状态,此时调用SaveChangesAsync才会真正提交Order的插入操作。
如果不提前查询Customer,DbContext内没有被追踪的Customer实体,Add Order时不存在隐式关联修复的过程,Order会被直接标记为Added状态,第一次SaveChanges就能正常提交,和问题描述的现象完全吻合。
当前代码存在的错误
- 仓储层滥用
Update()方法:如果Customer是被当前DbContext追踪查询出来的,EF Core会自动跟踪实体的属性变更,不需要手动调用Update()。手动调用Update()会强制将实体标记为Modified,同时遍历所有关联实体,极易引发不可预期的实体状态变更。 - 缺少统一的工作单元入口:两个仓储各自独立调用
SaveChangesAsync(),会导致单次业务操作多次提交数据库,破坏事务一致性,也容易出现实体状态在多次提交之间被隐式修改的问题。 - 关联实体处理不规范:新增关联实体时仅赋值外键,没有显式处理导航属性关联,在上下文已有追踪实体的场景下会触发EF Core的隐式关系修复,导致状态不符合预期。
正确实现方式
- 首先确认DbContext的注册生命周期为Scoped,和仓储保持一致,保证同一个请求内所有仓储共享同一个DbContext实例:
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString), ServiceLifetime.Scoped);
- 移除
SaveCustomer方法中冗余的dbContext.Customers.Update(customer)调用,只要实体被上下文追踪,EF Core会自动检测属性变更,无需手动标记状态。 - 新增关联Order时采用显式关联写法,避免隐式状态问题:
- 如果不需要操作Customer实体,直接赋值外键即可,不需要提前查询Customer(性能最优):
var newOrder = new Order() { CustomerId = customerId }; var orderId = await ordersRepository.CreateOrder(newOrder);- 如果已经查询了Customer实体,显式声明关联关系,不要只赋值外键:
var currentCustomer = await customersRepository.GetCustomer(customerId); var newOrder = new Order(); // 两种写法二选一即可 currentCustomer.Orders.Add(newOrder); // newOrder.Customer = currentCustomer; var orderId = await ordersRepository.CreateOrder(newOrder); - 建议引入工作单元(Unit of Work)模式,将
SaveChangesAsync的调用从仓储层剥离,由业务逻辑层统一触发单次提交,避免多次提交导致的事务碎片化和状态不一致问题。
内容的提问来源于stack exchange,提问作者goku_da_master
相关产品推荐
相关产品推荐

