我有一个流程,我定期从数据库中检索记录,并对每个记录运行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);
}
}
}
}
答案 0 :(得分:0)
这似乎是一种正确的做法,除了事实上,在捕获中你应该重新抛出异常或做其他事情来阻止循环进展(如果我理解你的要求)。甚至没有必要使用
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;
}
}