Может ли транзакция базы данных EF использоваться в цикле? - PullRequest
0 голосов
/ 07 мая 2018

У меня есть процесс, в котором я периодически получаю записи из базы данных и выполняю по 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); 
                }
            }
        }
    }

1 Ответ

0 голосов
/ 07 мая 2018

Кажется, это правильный способ сделать это, за исключением, возможно, того факта, что в 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;
        }
    }
...