【问题标题】:Can EF Database Transaction be used in a loop?EF 数据库事务可以循环使用吗?
【发布时间】:2018-05-07 12:02:10
【问题描述】:

我有一个过程,我定期从数据库中检索记录,并对每个记录运行 3 个操作。对于每条记录,3 个操作必须要么全部成功,要么根本不成功。如果其中一个操作失败,我希望已经为之前的记录处理过的操作 已提交,因此下次进程运行时,它会获取之前 3 个事务之一失败的记录。

我想过将这 3 个操作包装在每条记录的事务中,并为每条记录循环,但我想确保在这种情况下使用数据库事务是有效的。以下是心中所想。对吗?

    public async Task OrderCollectionProcessorWorker()
    {
        using (var context = new DbContext())
        {
            try
            {
                IList<Order> ordersToCollect =
                    await context.Orders.Where(
                        x => x.OrderStatusId == OrderStatusCodes.DeliveredId)
                              .ToListAsync(_cancellationTokenSource.Token);

                await ProcessCollectionsAsync(context, ordersToCollect);
            }
            catch (Exception ex)
            {
                Log.Error("Exception in OrderCollectionProcessorWorker", ex);
            }
        }
    }


    /// <summary>
    /// For each order to collect, perform 3 operations
    /// </summary>
    /// <param name="context">db context</param>
    /// <param name="ordersToCollect">List of Orders for collection</param>
    private async Task ProcessCollectionsAsync(DbContext context, IList<Order> ordersToCollect)
    {
        if (ordersToCollect.Count == 0) return;

        Log.Debug($"ProcessCollections: processing {ordersToCollect.Count} orders");

        foreach (var order in ordersToCollect)
        {
            // group the 3 operations in one transaction for each order
            // so that if one operation fails, the operations performend on the previous orders
            // are committed
            using (var transaction = context.Database.BeginTransaction())
            {
                try
                {
                    // *************************
                    // run the 3 operations here
                    // operations consist of updating the order itself, and other database updates
                    Operation1(order);
                    Operation2(order);
                    Operation3(order);

                    // *************************

                    await context.SaveChangesAsync();

                    transaction.Commit();
                }
                catch (Exception ex)
                {
                    transaction?.Rollback();                        
                    Log.Error("General exception when executing ProcessCollectionsAsync on Order " + order.Id, ex);
                    throw new Exception("ProcessCollections failed on Order " + order.Id, ex); 
                }
            }
        }
    }

【问题讨论】:

  • 根据您的代码,似乎每个操作都在运行自己的事务(请参阅事务是在 foreach 循环中创建的)。如果想要实现所有都应该提交或不提交的事情,那么将事务创建移出 foreach 循环。
  • 不,我想要一个事务中的所有 3 个操作,并为 IList 中的每个订单记录循环该操作。这 3 个操作将转到评论块所在的位置。

标签: c# .net database entity-framework transactions


【解决方案1】:

这似乎是一种正确的做法,除了在 catch 中您应该重新抛出异常或做其他事情以停止循环继续进行(如果我正确理解您的要求)之外。甚至没有必要使用

var transaction = context.Database.BeginTransaction()

因为

await context.SaveChangesAsync();

创建自己的事务。您所做的每项更改都存储在上下文中,当您调用 SaveChanges 时,会进行事务处理,并且所有更改都作为 1 批写入。如果发生故障,所有更改都将回滚。对 SaveChanges 的另一个调用将对新的更改进行另一个事务。但是请记住,如果事务失败,您不应再使用相同的上下文,而是创建一个新的上下文。总而言之,我会将您的方法编写如下:

private async Task ProcessCollectionsAsync(DbContext context, IList<Order> ordersToCollect)
{
    if (ordersToCollect.Count == 0) return;

    Log.Debug($"ProcessCollections: processing {ordersToCollect.Count} orders");

    foreach (var order in ordersToCollect)
    {
        // group the 3 operations in one transaction for each order
        // so that if one operation fails, the operations performend on the previous orders
        // are committed
        try
        {
            // *************************
            // run the 3 operations here
            // operations consist of updating the order itself, and other database updates
            Operation1(order);
            Operation2(order);
            Operation3(order);

            // *************************

            await context.SaveChangesAsync();
        }
        catch (Exception ex)
        {                     
            Log.Error("General exception when executing ProcessCollectionsAsync on Order " + order.Id, ex);
            throw;
        }
    }

【讨论】:

  • 对!忘记停止循环,将相应地更新代码。另外,是的,我似乎记得你提到的同样的事情。 SaveChanges 创建自己的事务,但不确定并想确认。非常感谢。
  • 在所有版本的 Entity Framework 中,每当您执行 SaveChanges() 以在数据库上插入、更新或删除时,框架都会将该操作包装在事务中。 msdn.microsoft.com/en-us/library/dn456843(v=vs.113).aspx
  • 那么什么时候可以使用BeginTransaction/commit/rollback?
  • 想象一下你想要嵌套事务。因此,您执行事务中的每一个小更改并将其单独保存,但是如果任何循环操作失败,您希望将所有更改恢复到开头 - 然后您可以将整个循环包装在外部事务中,这样您就可以恢复所有子更改一口气。
  • 清除,非常感谢。从某种意义上说,我最初使用事务是一种矫枉过正。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-28
  • 1970-01-01
  • 2012-04-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多