которые оба соединяющиеся с базой данных Northwind
, чтобы сделать изменения в таблице Product
. Метод ReduceStock()
сокращает объем запасов в столбце UnitsInStock
, метод IncreaseUnits()
увеличивает значение столбца UnitsOnOrder()
. Для обоих методов первым параметром является ProductID
в строке, которую нужно изменить, второй параметр есть величина, на которую мы хотим изменить соответствующий столбец.
Выполняющаяся транзакция контролируется булевой переменной CommitTrans
, передаваемой в PlaceOrder()
. Первая транзакция должна быть зафиксирована, так как уровень запаса для ProductID=2
равен 17, следовательно, можно удалить десять элементов и все еще иметь оставшийся запас. Однако вторая транзакция обречена на отказ так как ProductID=5
не имеет запаса элементов и существует ограничение на столбец UnitsInStock
, которое не позволяет значению становиться меньше нуля. Это означает, что можно проверить, будет ли транзакция отменяться или нет. Не должно быть никаких проблем с вызовом IncreaseStock()
, поэтому можно увидеть, что транзакция была отменена, проверяя значение столбца UnitsOnOrder
для ProductID=5
.
В блоке try
, если все идет хорошо, или, другими словами, если поток выполнения должен покинуть PlaceOrder()
нормально, через return true
, инструкция PlaceOrder()
вызывает метод SetComplete()
объекта ContextUtil
, эффективно сообщая DTC через менеджер ресурсов, что в той части, которая касается его, транзакцию необходимо зафиксировать.
С другой стороны, если где-то в PlaceOrder
возникает ошибка и порождается исключение, управление программой будет передано предложению catch()
. В этом предложении PlaceOrder()
вызовет метод SetAbort()
объекта ContextUtil
. Этот метод посылает голос PlaceOrder()
за отмену транзакции, в которую он вовлечен, и DTC после получения этого голоса от менеджера ресурсов прикажет каждому участнику транзакции отменить свою работу.
Помните, что не требуется создавать экземпляр объекта ContextUtil
, чтобы вызвать его методы SetComplete()
и SetAbort()
. Эти методы класса, поэтому их можно вызывать прямо на классе.
Практически любой код, поддерживающий транзакции будет походить на код в примере. Он вызывает SetComplete()
перед своей точкой выхода для фиксации всей работы, которая была успешно выполнена, или он вызывает метод SetAbort()
класса ContextUtil
в своем обработчике ошибок, чтобы все отменить в связи с ошибкой. Существует, правда, еще более простой способ.
Компания Microsoft предоставляет атрибут .NET, называемый AutoComplete
. Методы, модифицированные с помощью этого атрибута, автоматически применяют подход, описанный выше. И хотя такие методы никогда явно не ссылаются на класс ContextUtil
, они неявно завершают свои транзакции, если те заканчиваются нормально, или отменяют всю работу если выход происходит в связи с ошибкой (когда порождается исключение). По прежнему необходимо вызывать SetAbort()
, чтобы отменить работу транзакции если порождается исключение.
[AutoComplete]
public bool PlaceOrder(bool CommitTrans) {
try {
if (CommitTrans) {
// Эта транзакция должна быть зафиксирована
// шаг 1 — Увеличить число единиц продукта ID=2 на 10
IncreaseUnits(2, 10);
// шаг 2 - Сократить запас продукта ID=2 на 10 единиц
ReduceStock(2, 10);
} else {
// Эта транзакция должна быть отменена
// шаг 1 - Увеличить число единиц продукта ID=5 на 5
IncreaseUnits(5, 5);
// шаг 2 — Сократить запас продукта ID=5 на 5 единиц
ReduceStock(5, 5);
}
return true;
} catch (Exception e) {
ContextUtil.SetAbort();
return false;
}
}
Это полный код примера всей транзакции, он показывает, как все части объединяются. Следующее просто встроено в библиотеку классов с заданным сильным именем и зарегистрировано в глобальном кэше сборок.
using System;
using System.EnterpriseServices;
using System.Data.SqlClient;
namespace OrderTransaction {
[Transaction(TransactionOptiоn.Required)]
public class Purchase : ServicedComponent {
public Purchase() { }
public bool PlaceOrder(bool CommitTrans) {
// Попытка работы
try {
if (CommitTrans) {
// Эта транзакция должна быть зафиксирована
// шаг 1 - Увеличить число единиц продукта ID=2 на 10
IncreaseUnits(2, 10);
// шаг 2 - Сократить запас продукта ID=2 на 10 единиц
ReduceStock(2, 10);
} else {
// Эта транзакция должна быть отменена
// шаг 3 — Увеличить число единиц продукта ID=5 на 5
IncreaseUnits(5, 5);
// шаг 2 — Сократить запас продукта ID=5 на 5
единиц ReduceStock(5, 5);
}
// Если все прошло хорошо, закончить транзакцию.
ContextUtil.SetComplete();
return true;
}
// Этот код выполняется, если встречается ошибка.
catch (Exception e) {
// Отменить работу, которую выполнила эта функция.
ContextUtil.SetAbort();
return false;