EF:db.SaveChanges()与dbTransaction.Commit [英] EF: db.SaveChanges() vs dbTransaction.Commit

查看:219
本文介绍了EF:db.SaveChanges()与dbTransaction.Commit的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对实体框架还很陌生,我对EF的db.SaveChange表示怀疑。从一些帖子和MSDN中,我了解到 db.SaveChange 默认情况下会进行事务中的所有更改。还有一种方法可以使用 db.Database.BeginTransaction()创建自己的事务, db是我的上下文类对象。所以我有两个问题:

I am fairly new to entity framework and I have a doubt on EF's db.SaveChange. From some posts and MSDN I learned that db.SaveChange by default does all the changes in transaction. Also there is a way we can create a transaction of our own using db.Database.BeginTransaction() ,"db" being my context class object. So I have two questions:


  1. 使用什么& when

  2. 如果我将数据插入到@@ identity是我的下一个插入表的外键的表中,而不是使用 db.SaveChange()获取@@ identity还有其他方法( db.SaveChanges()在用户定义的事务范围内),并且将 db.SaveChanges()将我的更改提交给数据库

  1. What to use & when
  2. If I am inserting data to one table whose @@identity is foreign key to my next inserting table, rather than using db.SaveChange() to get the @@identity is there any other way (db.SaveChanges() is in a user defined transaction scope) and will db.SaveChanges() commit my changes to DB


推荐答案

是的,如果您将上下文明确地包装在诸如.Net的TransactionScope之类的事务中,则可以在调用.SaveChanges()之后从实体中检索自动生成的ID,而无需提交范围限定的事务。

Yes, if you explicitly wrap your context within a transaction such as .Net's TransactionScope, you can retrieve auto-generated IDs from entities after a .SaveChanges() call, without committing the scoped transaction.

using (var tx = new TransactionScope())
{
  using (var context = new MyDbContext())
  {
     var newEntity = populateNewEntity();
     context.MyEntities.Add(newEntity);
     context.SaveChanges();
     int entityId = newEntity.EntityId; // Fetches the identity value.
  }
} // Rolls back the transaction. Entity not committed.

但是,除非绝对必要,否则应避免此类操作,并应谨慎。首先,以上示例是TransactionScope的常用用法,TransactionScope的默认隔离级别为可序列化,这在锁定方面是最悲观的。即使在具有大量并发操作/用户的系统上适度使用此模式,也会由于锁定等待而导致死锁和性能下降。因此,如果使用TransactionScope,请确保指定隔离级别。

However, operations like this should be avoided unless absolutely necessary, and cautiously. Firstly, the above example is common use of TransactionScope, and the default isolation level of TransactionScope is "Serializable" which is the most pessimistic in terms of locking. Even moderate use of this pattern on systems that have a number of concurrent operations/users will result in deadlocks and performance hits due to lock waits. So if using a TransactionScope, be sure to specify an isolation level.

DTC在要协调数据库或其他受Tx绑定的操作之间的提交的情况下很有用。例如,系统A正在保存更改,并且需要通过API与系统B协调更新/插入。 & B需要配置为使用DTC,但一旦完成,A就可以开始交易,向DTC注册,将DTC令牌附加到B的API的标头中,B可以找到该令牌,创建一个链接到该令牌的ScopedTransaction,并根据A信号进行提交/回滚。这具有间接费用,这意味着两个系统上的事务处理的开放时间都比平时更长。如果有必要,那是业务成本。如果不是必须的话,那将是浪费和潜在的头痛根源。

DTC is useful in scenarios where you want to coordinate commits between databases or other Tx-bound operations. For instance system A is saving changes and needs to coordinate an update/insert with system B through an API. A & B need to be configured to use DTC, but once that is done A can start a transaction, register it with DTC, append the DTC token to the header for B's API, B can find that token, create a ScopedTransaction linked to that token, and commit/rollback based on what A signals. This has an overhead cost meaning transactions on both systems are open longer than usual. If it's necessary then that is a cost of business. If it's not necessary then it is a waste and potential source of headaches.

有人可能会考虑使用显式Tx的另一个原因是他们想更新FK。相关实体。创建订单可以选择创建新客户,订单具有客户ID,因此我们需要创建客户,获取要在订单上设置的ID,然后保存订单。如果订单保存失败,则客户创建应回滚。

One other reason that someone might look at using an explicit Tx is when they want to update FK's in a related entity. Creating an order has an option to create a new customer, order has a customer ID so we need to create the customer, get it's ID to set on the Order, then save the order. If the order save fails then the customer creation should roll back.

using (var tx = new TransactionScope())
{
  using (var context = new MyDbContext())
  {
     var newCustomer = createNewCustomer(); // dummy method to indicate creating a customer entity.
     context.Customers.Add(newCustomer);
     context.SaveChanges();
     var newOrder = createNewOrder(); 
     newOrder.CustomerId = newCustomer.CustomerId;
     context.Orders.Add(newOrder);
     context.SaveChanges();
  }
  tx.Commit();  
} 

使用EF时,应通过使用导航属性以及顺序之间的关系来缓解这种情况和客户。这样,您可以创建客户,创建订单,将订单的客户引用设置为新客户,将订单添加到DbContext以及.SaveChanges()。这样一来,EF便可以处理订单,查看被引用的客户,进行插入,将FK与订单相关联,并在一个隐含的Tx中提交更改。

With EF this scenario should be mitigated by using navigation properties with a relationship between order and customer. In this way you can create a customer, create the order, set the order's Customer reference to the new customer, add the order to the DbContext, and .SaveChanges(). This lets EF take care of going through the order, seeing the referenced customer, inserting that, associating the FK in the order, and committing the changes in one implicit Tx.

using (var context = new MyDbContext())
{
    var newCustomer = createNewCustomer();
    var newOrder = createNewOrder();
    newOrder.Customer = newCustomer;
    context.Orders.Add(newOrder);
    context.SaveChanges();
}

更新:概述在您的实体中避免FK引用...(许多-

Update: To outline avoiding FK references in your entities... (many-to-one)

EntityTypeConfiguration for实体中带有FK的订单:

EntityTypeConfiguration for Order With FK in entity:

HasRequired(x => x.Customer)
  .WithMany(x => x.Orders) // Links to an element in the Orders collection of the Customer. If Customer does not have/need an Orders collection then .WithMany()
  .HasForeignKey(x => x.CustomerId); // Maps Order.Customer to use CustomerId property on Order entity.

实体中没有FK的订单的EntityTypeConfiguration:

EntityTypeConfiguration for Order With No FK in entity:

HasRequired(x => x.Customer)
  .WithMany(x => x.Orders)
  .Map(x => x.MapKey("CustomerId")); // Maps Order.Customer to use CustomerId column on underlying Order table. Order entity does not expose a CustomerId.

使用EF核心-从内存中,可能需要更新。

With EF Core -- From memory, may need to be updated.

HasRequired(x => x.Customer)
  .WithMany(x => x.Orders) // Links to an element in the Orders collection of the Customer. If Customer does not have/need an Orders collection then .WithMany()
  .HasForeignKey("CustomerId"); // Creates a shadow property where Entity does not have a CustomerId property.

两种方法(带有或不带有映射FK)的工作原理相同。第二种方法的好处是,代码中没有混淆如何更新或评估订单的客户参考。例如,如果您在订单上既有客户又有客户ID,则更改客户ID并调用SaveChanges不会将订单移动到新客户,仅设置客户参考即可。设置客户参考不会自动更新CustomerId,因此,在刷新实体之前,通过订单上的CustomerId属性获取 customerId的任何代码仍将检索旧的客户参考。

Both approaches (with or without mapped FK) work the same. The benefit of the second approach is that there is no confusion in the code about how to update or assess the customer reference for the order. For example if you have both a Customer, and a CustomerId on the Order, changing the CustomerId and calling SaveChanges does not move the order to a new customer, only setting the Customer reference. Setting the Customer reference does not automatically update the CustomerId, so any code "getting" the customerId via the CustomerId property on order would still retrieve the old customer reference until the entity is refreshed.

使用导航属性的重要之处在于延迟执行或有效地加载它们。例如,如果要加载订单列表并包括其客户名称:

The important thing to using navigation properties is to leverage them with deferred execution or eager-load them efficiently. For example if you want to load a list of orders and include their customer name:

using (var myContext = new MyDbContext())
{
  var orders = myContext.Orders.Where(x => x.OrderDate >= startDate && x.OrderDate < endDate).ToList();
  return orders;
}

**错误:如果是MVC / Web API,则序列化程序将使用订单集合,并尝试对其进行序列化,请点击每个导航属性并尝试加载它。这将触发延迟加载调用。因此,如果Order有一个Customer,那将是对数据库的命中/ w SELECT * FROM Customers WHERE CustomerId = 42如果Order具有Order行,则 SELECT * FROM OrderLines WHERE OrderLineId = 121, SELECT * FROM OrderLines WHERE OrderLineId = 122 ...((您可能会想知道要通过OrderId来获取订单行,但不对!返回实体的巨大性能影响,只是不要这样做。

** Bad: If this is MVC/Web API the serializer will take the orders collection, and attempting to serialize them hit every navigation property and attempt to load it. This triggers lazy-load calls one-by-one. So if Order has a Customer, that is a hit to the DB /w "SELECT * FROM Customers WHERE CustomerId = 42" If Order has Order lines then "SELECT * FROM OrderLines WHERE OrderLineId = 121", "SELECT * FROM OrderLines WHERE OrderLineId = 122" ... (You might think it'd know to fetch order lines by OrderId, but nope! Huge performance impact returning Entities, just don't do it.

using (var myContext = new MyDbContext())
{
  var orders = myContext.Orders
    .Include(x => x.Customer)
    .Include(x => x.OrderLines)
    .Where(x => x.OrderDate >= startDate && x.OrderDate < endDate).ToList();
  return orders;
}

**更好,但仍然很糟糕。可能只包含您认为需要的项目,但是序列化程序仍会获取订单上的所有内容。当实体经过修改以包含新的数据链接时,这会再次咬住您。即使您包含所有内容,这也很浪费

** Better, but still bad. You might only include the items you think you'll need, but the serializer will still fetch everything on the order. This comes back to bite you as entities are revised to include new links to data. Even if you Include everything this is wasteful if all you wanted was the Customer Name.

using (var myContext = new MyDbContext())
{
  var orders = myContext.Orders
    .Where(x => x.OrderDate >= startDate && x.OrderDate < endDate)
    .Select(x => new OrderLineViewModel 
    {
      OrderId = x.OrderId,
      OrderNumber = x.OrderNumber,
      OrderAmount = x.OrderAmount,
      CustomerName = x.Customer.Name
    }).ToList();
  return orders;
}

**这是具有导航属性和延迟执行的最佳选择。在数据库上运行的SQL仅从相关数据中返回这4列。没有延迟负载,您只需通过电线发送所需的数据量即可。

** This is the sweet spot with navigation properties and deferred execution. The SQL that gets run on the DB returns just those 4 columns from the related data. No lazy load hits, and you send across the wire just the amount of data you need.

有些人可能会争辩说,例如,如果您通常需要从Order中获取CustomerId引用,在Order实体上具有CustomerId的文件可以节省引用客户的时间。但是如上所述,该ID可能并不可靠,并且通过使用延迟执行让EF使用实体来填充您想要的数据,获取订单的客户ID只需添加/选择 x .Customer.CustomerId 仅包含所需的列,而不是加载整个实体来获取它。

Some might argue that if you commonly need a CustomerId reference from an Order for example that having a CustomerId on the Order entity saves referencing the Customer. But as outlined above, that Id may not be reliable, and by using deferred execution to let EF use the entities to populate the data you want Getting the customer IDs of orders is just a matter of including/selecting x.Customer.CustomerId which includes just that desired column, not loading the entire entity to get it.

这篇关于EF:db.SaveChanges()与dbTransaction.Commit的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆