在 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 个实体时的常见问题。)
我有一个使用 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 个实体时的常见问题。)