我正在使用C#将发票的平面文件导入数据库。如果遇到问题,我正在使用TransactionScope回滚整个操作。
这是一个棘手的输入文件,因为一行不必等于一个记录。它还包括链接记录。发票将包含标题行,行项目,然后是总行。有些发票需要被跳过,但我可能不知道它需要被跳过,直到我达到总线。
一种策略是将标题,行项和总行存储在内存中,并在达到总行后保存所有内容。我现在正在追求这一点。
但是,我想知道是否可以采用不同的方式。在发票周围创建“嵌套”事务,插入标题行和行项目,然后在达到总行时更新发票。如果确定需要跳过发票,则此“嵌套”交易将回滚,但整个交易将继续。
这是可行的,实用的,你会如何设置?
答案 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
的不同的交易,但不嵌套。如果失败,outer
(DoOuterWork()
)和任何其他作用域中发生的工作仍然可以提交,但这里有一个问题:如果完成,则回滚整个{ {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)
内部事务失败会回滚外部事务,因此您无法执行该路由。
但是,您可以通过使用临时(或加载)表来伪造它。将每个发票事务性地插入到装入表中,然后以原子方式从装入表移动到永久表。