Я импортирую плоский файл счетов в базу данных с помощью C#. Я использую TransactionScope для отката всей операции, если с проблемой встречаются.
Это - хитрый входной файл, в котором одна строка не делает необходимый равный одна запись. Это также включает связанные записи. Счет имел бы строку заголовка, позиции и затем общую строку. Некоторые счета должны будут быть пропущены, но я не могу знать, что это должно быть пропущено, пока я не достигаю общей строки.
Одна стратегия состоит в том, чтобы сохранить заголовок, позиции и общую строку в памяти, и сохранить все, после того как общая строка достигнута. Я преследую это теперь.
Однако я задавался вопросом, могло ли это быть сделано другой путь. Создание "вложенной" транзакции вокруг счета, вставка строки заголовка и позиций, затем обновление счета, когда общая строка достигнута. Эта "вложенная" транзакция откатывала бы, если бы определено, что счет должен быть пропущен, но полная транзакция продолжилась бы.
Действительно ли это возможно, практично, и как Вы настроили бы это?
Это выполняется с помощью транзакции точка сохранения . Обычно это выглядит примерно так:
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 или вы можете использовать хранимые процедуры T-SQL, смоделированные на основе безопасного шаблона . Я бы рекомендовал вам избегать транзакций .Net (например, TransactionScope) в этом случае.
Ни 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
выполняется в неявной транзакции, независимо от external
. Ничего из того, что происходит в DoWork1
, не будет атомарным. Если это не удастся на полпути, у вас будут противоречивые данные. Любая работа, которая здесь происходит, всегда совершается, независимо от того, что происходит с внешним
.
inner2
выполняется в новой транзакции, независимо от external
.Эта транзакция отличается от внешней
, но она не вложенная. В случае сбоя работа, выполненная в внешнем
( DoOuterWork ()
) и любой другой области действия, все еще может быть зафиксирована, но вот загвоздка: Если она завершится, затем откат всей внешней
транзакции не откатит назад работу, выполненную внутри inner2
. Вот почему он не является действительно вложенным. Кроме того, inner2
не будет иметь доступа ни к каким строкам, заблокированным external
, поэтому вы можете столкнуться с тупиками здесь, если не будете осторожны.
inner3
выполняется в той же транзакции, что и external
. Это поведение по умолчанию. Если DoWork3 ()
терпит неудачу и inner3
никогда не завершается, то выполняется откат всей внешней
транзакции. Точно так же, если inner3
завершается успешно, но external
откатывается, то любая работа, выполненная в DoWork3 ()
, также откатывается.
Итак, вы можете надеяться, что ни одна из этих опций на самом деле не вложена и не даст вам того, что вы хотите. Параметр Required
приближается к вложенной транзакции, но не дает вам возможности независимо фиксировать или откатывать определенные единицы работы внутри транзакции.
Наиболее близким к истинным вложенным транзакциям в SQL Server является оператор SAVE TRAN
в сочетании с некоторыми блоками TRY / CATCH
.Если вы можете поместить свою логику в одну или несколько хранимых процедур, это будет хорошим вариантом.
В противном случае вам нужно будет использовать отдельные транзакции для каждого счета в соответствии с предложением Одеда.
Неудачная внутренняя транзакция приведет к откату внешней транзакции, поэтому вы не сможете пойти по этому пути.
Вы, вероятно, можете подделать это, используя временную (или загрузочную) таблицу. Вставьте каждый счет-фактуру транзакционно в таблицу загрузки, а затем атомарно переходите из таблицы загрузки в постоянную таблицу.
Вместо использования вложенных транзакций вы можете создать транзакцию для каждого счета-фактуры. Таким образом, будут происходить только успешные обновления для всех счетов.
Если вы будете вкладывать транзакции так, как вы описываете, вы рискуете получить откат для всего набора данных, а это не то, что вам нужно.
Лично я бы сначала посмотрел, нужно ли добавить счет-фактуру - если да, то сделайте вставку (в транзакции). В противном случае просто переходите к следующему счету-фактуре.
Я не думаю, что это так уж здорово - вставлять, а затем делать откат описанным вами способом.