在 Entity Framework UnitOfWork 模式中实施事务

Implementing Transaction in Entity Framework UnitOfWork patterns

我有一个使用 MVC 和 EF 的网络应用程序。我正在使用 Microsoft 在线文档中的 Repository and Unit of Work Patterns。 我正在尝试从多个表中插入多行。 代码看起来像这样:

unitOfWork.Table1.Insert(row1);
unitOfWork.Save();//recId is primary key, will be auto generated after save.

table2Row.someId = table1Row.recId;
unitOfWork.Table2.Insert(row2);
unitOfWork.Save();

如果插入row2出错,我需要回滚row1和row2。 如何使用 UnitOfWork 模式实施 BeginTransaction/Commit/Rollback?

谢谢。

为避免这些问题,最好将 EF 用作 ORM 而不是简单的数据转换服务,方法是使用引用属性而不是直接依赖于设置 FK 值。

您的示例似乎只是提供了 DbContext 的精简抽象。

举一个新客户的订单示例,您希望在订单中显示 CustomerId。

问题:新客户的ID是由数据库生成的,所以只有在SaveChanges之后才能使用。

解决方案:使用 EF 导航属性并让 EF 管理 FK。

var customer = new Customer
{
   Name = customerName,
   // etc.
};
var order = new Order
{
    OrderNumber = orderNumber,
    // etc.
    Customer = customer,
};
dbContext.Orders.Add(order);
dbContext.SaveChanges();

请注意,我们不必将客户显式添加到 dbContext,它将通过订单的客户引用添加,订单 table 的 CustomerId 将自动设置。如果上下文中有 Customers DbSet,您也可以显式添加客户,但只需要一个 SaveChanges 调用。

要设置导航属性,请参阅:

** 编辑(基于对一对多关系的评论)** 对于集合,要避免的一些常见陷阱包括直接为已经与 DbContext 关联的实体设置集合引用,以及利用虚拟引用以便 EF 能够最好地管理集合中实例的跟踪。

如果一个订单有多个客户,那么您将在订单中有一个客户集合:

public virtual List<Customer> Customers{ get; set; } = new List<Customer>();

您的客户中的可选订单参考:

public virtual Order Order { get; set; }

映射这些看起来像:(从订单的角度来看)

HasMany(x => x.Customers)
  .WithRequired(x => x.Order)
  .Map(x => x.MapKey("OrderId"));

如果客户没有订单参考,则替换为 .WithRequired()

这是基于实体没有声明 FK 字段的关系。如果您声明 FK,则 .Map(...) 变为 .HasForeignKey(x => x.FkProperty)

如果您要创建新订单,请从那里开始:

var order = new Order 
{ 
  OrderNumber = "12345", 
  Customers = new [] 
  { 
    new Customer { Name = "Fred" }, 
    new Customer { Name = "Ginger" }
  }.ToList()
};
using (var context = new MyDbContext())
{
   context.Orders.Add(order);
   context.SaveChanges();
}

这应该都按预期工作。它将保存新订单和两个关联的客户。

但是,如果您从 DbContext 加载订单并想要操作与其关联的客户,则需要注意几点。 1. 您应该预先加载 Customers 集合,以便 EF 了解这些实体。 2. 您需要使用 Add/Remove 而不是设置引用来操作集合,以避免混淆代码的外观和 EF 解释的内容。

类似于:

using (var context = new MyDbContext())
{
   var order = context.Orders.Find(1);
   order.Customers = new [] 
   { 
      new Customer { Name = "Roy" }
   }.ToList();
   context.SaveChanges();
}

将导致 "Roy" 被添加到客户中,而不是替换他们。

要替换它们,您需要先删除它们,然后再添加新的。

using (var context = new MyDbContext())
{
   var order = context.Orders.Find(1);
   context.Customers.RemoveRange(order.Customers); // Assuming customers cannot exist without orders. If OrderId is nullable, this line can be omitted.
   order.Customers.Clear(); 
  order.Customers,Add(new Customer { Name = "Roy" });
   context.SaveChanges();
}

如果集合不是虚拟的,这就会开始崩溃。例如:

using (var context = new MyDbContext())
{
   var order = context.Orders.Find(1);
   order.Customers = new [] 
   { 
      new Customer { Name = "Roy" }
   }.ToList();
   context.SaveChanges();
}

如果客户集合是虚拟的,在 SaveChanges 之后,order.Customers 将报告 3 个元素的集合大小。如果它不是虚拟的,它会将大小报告为 1 个元素,即使现在有 3 条记录与数据库中的订单相关联。这会导致项目因无效数据状态、重复记录等问题而陷入困境。

您看到某些记录丢失的情况可能是由于缺少引用上的虚拟、在 EF 正在跟踪的范围之外操作集合或对跟踪状态进行其他操作。 (当项目设置为来自上下文的 detach/re-attach 个实体时的常见问题。)