在实体框架UnitOfWork模式中实现事务

时间:2018-07-10 15:21:19

标签: asp.net-mvc entity-framework transactions foreign-keys unit-of-work

我有一个使用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?

谢谢。

1 个答案:

答案 0 :(得分:0)

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

您的示例似乎并没有提供比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();

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

要设置导航属性,请参见:https://stackoverflow.com/a/50539429/423497

**编辑(基于关于一对多关系的评论)** 对于集合,应避免的一些常见陷阱包括直接为已经与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.您应该急于加载“客户”集合,以便EF了解这些实体。 2.您需要使用“添加/删除”来操作集合,而不是设置引用,以避免混淆代码的外观和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之后订购。客户将报告集合大小为3个元素。如果它不是虚拟的,则即使数据库中有3条记录与此订单相关联,它也会将大小报告为1个元素。这会导致各种问题,其中项目会因无效的数据状态,重复的记录等而陷入困境。

在某些情况下会丢失一些记录的情况可能是由于缺少参考上的虚拟,操纵EF所跟踪的集合之外的其他集合或对跟踪状态的其他操纵。 (将项目设置为从上下文分离/重新附加实体时,这是一个常见问题。)