C#中的交易内的交易

时间:2010-05-08 16:21:37

标签: .net sql-server transactions

我正在使用C#将发票的平面文件导入数据库。如果遇到问题,我正在使用TransactionScope回滚整个操作。

这是一个棘手的输入文件,因为一行不必等于一个记录。它还包括链接记录。发票将包含标题行,行项目,然后是总行。有些发票需要被跳过,但我可能不知道它需要被跳过,直到我达到总线。

一种策略是将标题,行项和总行存储在内存中,并在达到总行后保存所有内容。我现在正在追求这一点。

但是,我想知道是否可以采用不同的方式。在发票周围创建“嵌套”事务,插入标题行和行项目,然后在达到总行时更新发票。如果确定需要跳过发票,则此“嵌套”交易将回滚,但整个交易将继续。

这是可行的,实用的,你会如何设置?

5 个答案:

答案 0 :(得分:32)

TransactionScope和SQL Server都不支持嵌套事务。

您可以嵌套TransactionScope个实例,但只有嵌套事务的外观。实际上,有一种叫做“环境”的交易,一次只能有一种。环境事务的哪个事务取决于您在创建范围时用于TransactionScopeOption的内容。

要更详细地解释,请考虑以下事项:

using (var outer = new TransactionScope())
{
    DoOuterWork();

    using (var inner1 = new TransactionScope(TransactionScopeOption.Suppress))
    {
        DoWork1();
        inner1.Complete();
    }

    using (var inner2 = new TransactionScope(TransactionScopeOption.RequiresNew))
    {
        DoWork2();
        inner2.Complete();
    }

    using (var inner3 = new TransactionScope(TransactionScopeOption.Required))
    {
        DoWork3();
        inner3.Complete();
    }

    outer.Complete();
}

以下是每个内部范围的内容:

  • inner1在隐式事务中执行,与outer无关。 DoWork1中发生的任何事情都不会保证是原子的。如果在中途失败,您将获得不一致的数据。无论outer发生了什么,

  • 都会始终提交此处发生的任何工作
  • inner2在新的交易中执行,与outer无关。这是来自outer不同的交易,但嵌套。如果失败,outerDoOuterWork())和任何其他作用域中发生的工作仍然可以提交,但这里有一个问题:如果完成,则回滚整个{ {1}}事务将回滚outer内完成的工作。这就是为什么它不是真正嵌套的原因。此外,inner2将无法访问由inner2锁定的任何行,因此如果您不小心,最终可能会遇到死锁。

  • outer在与inner3相同的相同交易中执行。这是默认行为。如果outer失败并且DoWork3()永远不会完成,则会回滚整个inner3事务。同样,如果outer成功完成但inner3已回滚,那么outer中完成的任何工作也会回滚。

所以你可以希望看到这些选项都没有实际嵌套,并且不会给你你想要的东西。 DoWork3()选项近似于嵌套事务,但不允许您在事务内独立提交或回滚特定工作单元。

您可以在SQL Server中获得真正嵌套事务的最接近的事情是Required语句与一些SAVE TRAN块组合在一起。如果您可以将逻辑放在一个或多个存储过程中,这将是一个不错的选择。

否则,根据Oded的建议,您需要为每张发票使用单独的交易。

答案 1 :(得分:3)

这是通过transaction savepoint完成的。它通常看起来像这样:

BEGIN TRANSACTION
for each invoice
   SAVE TRANSACTION InvoiceStarted
   BEGIN TRY
     Save header
     Save line 1
     Save line 2
     Save Total
   END TRY
   BEGIN CATCH
     ROLLBACK TO Invoicestarted 
     Log Failed Invoice
   END CATCH
end for
COMMIT

我使用了基于Transact-SQL的伪代码,这不是偶然的。保存点是数据库概念,.Net事务不支持它们。您可以直接使用SqlTransaction并利用SqlTransaction.Save,也可以使用在exception safe template之后建模的T-SQL存储过程。我建议你在这种情况下避免使用.Net事务(即TransactionScope)。

答案 2 :(得分:2)

您可以为每张发票创建一个事务,而不是使用嵌套事务。这样,只会对整个发票进行成功更新。

如果您按照描述的方式嵌套事务,则存在整个数据集被回滚的危险,这不是您想要的。

答案 3 :(得分:2)

就个人而言,我首先要看是否需要添加发票 - 如果是,则执行插入(在事务中)。否则,只需转到下一张发票即可。

我认为插入然后以您描述的方式进行回滚并不是那么好。

答案 4 :(得分:0)

内部事务失败会回滚外部事务,因此您无法执行该路由。

但是,您可以通过使用临时(或加载)表来伪造它。将每个发票事务性地插入到装入表中,然后以原子方式从装入表移动到永久表。