ASP.NET Web API中带外键的Unit of Work模式实现疑问
我觉得这个帖子的问题可能提错了。请查看我关于该主题的新帖:ASP.NET Web API - Service Between Controller and Repository?
我正尝试实现Repository/Unit of Work模式。
我的目标是让控制器能够通过各个Repository写入多个实体,但仅在最后通过Unit of Work类中的方法调用一次context.SaveChanges(),这样的优势是仅会产生一个数据库事务,若其中一个写入操作失败,所有操作都会回滚。
示例代码如下:
public class Customer { public int CustomerID { get; set; } public string Name { get; set; } } public class Order { public int OrderId { get; set; } public int CustomerId { get; set; } // foreign key to Customer ... }
我见过的示例都是写入完全无关的表,但实际场景中会遇到需要写入两个表,且第二个表需要引用第一个表外键的情况。(注:上述例子可能不是最佳比喻,因为通常客户下单前就已存在,但我的场景涉及写入包含多个类型的数据图。)
显然我希望所有操作都在同一个事务中完成,但问题在于,只有调用context.SaveChanges()后才能获取刚创建的Customer的ID(用于写入Order表的外键),这就打破了单事务的要求。
有没有人遇到过这种情况?如果有,是如何解决的?谢谢。
你完全不需要提前调用SaveChanges()来获取新实体的ID,EF Core在内存中就能处理这种关联关系,以下是几种可行方案:
使用实体导航属性关联
给实体添加导航属性,直接关联对象而非手动设置外键ID:public class Customer { public int CustomerID { get; set; } public string Name { get; set; } // 导航属性:关联该客户的所有订单 public ICollection<Order> Orders { get; set; } = new List<Order>(); } public class Order { public int OrderId { get; set; } public int CustomerId { get; set; } // 导航属性:关联订单所属的客户 public Customer Customer { get; set; } }业务逻辑中直接关联对象:
var customer = new Customer { Name = "Test Customer" }; var order = new Order { /* 设置订单其他属性 */ }; // 关联订单到客户,二选一即可 customer.Orders.Add(order); // order.Customer = customer; // 仅需将客户添加到Repository,EF Core会自动跟踪关联的订单 _customerRepository.Add(customer); _unitOfWork.SaveChanges();调用
SaveChanges()时,EF Core会先插入Customer,获取生成的真实ID后自动填充到Order的CustomerId字段,再插入Order,整个过程在同一个事务中完成。无导航属性的场景处理
若不想使用导航属性,可先将Customer添加到上下文,EF Core会为其生成临时ID,直接用这个临时ID设置Order的外键即可:var customer = new Customer { Name = "Test Customer" }; _customerRepository.Add(customer); // 此时EF已跟踪该客户,即使未SaveChanges也有临时ID var order = new Order { CustomerId = customer.CustomerID /* 其他属性 */ }; _orderRepository.Add(order); _unitOfWork.SaveChanges();执行
SaveChanges()时,EF Core会自动将临时ID替换为数据库生成的真实ID,确保外键关联正确,全程在一个事务内。显式事务控制(可选)
若需要更精细的事务控制,可在Unit of Work中显式开启事务:using var transaction = await _context.Database.BeginTransactionAsync(); try { _customerRepository.Add(customer); _orderRepository.Add(order); await _unitOfWork.SaveChangesAsync(); await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw; }注意:默认情况下
SaveChanges()本身就会在一个事务中执行所有待处理操作,大部分场景无需额外显式开启事务。
内容的提问来源于stack exchange,提问作者FunkMonkey33

