Транзакции
67LINQ --- LINQ to DataSet и SQL --- Транзакции
»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ
Раньше уже говорилось о том, что при отсутствии активной транзакции во время вызова метода SubmitChanges будет создана новая транзакция. В результате все попытки модификаций базы данных, произведенные в течение единственного вызова SubmitChanges, будут помещены в контекст единой транзакции. Это очень удобно, но что если нужна транзакция, распространяющаяся за пределы SubmitChanges?
Давайте рассмотрим пример, демонстрирующий, как можно поместить обновления, сделанные несколькими вызовами метода SubmitChanges в одну транзакцию. Более того, вызовы метода SubmitChanges будут обновлять разные таблицы.
В следующем примере вносятся изменения в записи базы данных Northwind и базы данных TestDB, созданной в предыдущей статье. Обычно каждый вызов метода SubmitChanges на каждом из этих объектов DataContext должен быть помещен внутрь собственной индивидуальной транзакции. В приведенном примере оба вызова метода SubmitChanges помещаются в одну транзакцию:
// Используйте свое подключение
Northwind db = new Northwind(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
Initial Catalog=C:\NORTHWIND.MDF;
Integrated Security=True");
TestDB testDB = new TestDB(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
Initial Catalog=TestDB;Integrated Security=SSPI;");
Customer cust = db.Customers.Where(c => c.CustomerID == "LONEP").SingleOrDefault();
cust.ContactName = "Barbara Penczek";
Rectangle rect = (Rectangle)testDB.Shapes.Where(s => s.Id == 3).SingleOrDefault();
rect.Width = 15;
try
{
using (System.Transactions.TransactionScope scope =
new System.Transactions.TransactionScope())
{
db.SubmitChanges();
testDB.SubmitChanges();
throw (new Exception("Just to rollback the transaction."));
// Здесь компилятор выдаст предупреждение,
// поскольку следующая строка кода недостижима
scope.Complete();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
db.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, cust);
Console.WriteLine("Contact Name = {0}", cust.ContactName);
testDB.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, rect);
Console.WriteLine("Rectangle Width = {0}", rect.Width);
Для работы этого примера в проект должна быть добавлена ссылка на библиотеку System.Transactions.dll.
В приведенном коде для каждой базы данных создается объект DataContext. Затем из каждой базы запрашивается по сущностному объекту, в который вносятся изменения.
Далее создается экземпляр объекта TransactionScope, так что появляется объемлющая транзакция для включения объектов DataContext с их вызовами метода SubmitChanges. После вызова метода SubmitChanges на каждом DataContext намеренно генерируется исключение, так что метод scope.Complete не вызывается и происходит откат транзакции.
Имейте в виду, что поскольку здесь после генерации исключения присутствует код, компилятор выдаст предупреждение, т.к. вызов метода scope.Complete недостижим для выполнения.
Если не поместить вызовы метода SubmitChanges в контекст объекта TransactionScope, то каждый вызов SubmitChanges получит собственную транзакцию, и его изменения будут зафиксированы немедленно по окончании успешного вызова SubmitChanges.
Как только в предыдущем коде генерируется исключение, транзакция выходит из контекста, и поскольку метод Complete не был вызван, транзакция откатывается. В этой точке все изменения, внесенные в базе данных, отменяются.
Важно помнить, что несмотря на то, что изменения не были успешно сохранены в базе данных, сущностные объекты продолжают содержать модифицированные данные. Помните, что даже если метод SubmitChanges не был завершен успешно, изменения поддерживаются в сущностных объектах, чтобы можно было разрешить конфликты параллельного доступа и снова вызвать метод SubmitChanges.
В этом случае вызовы метода SubmitChanges завершены успешно. Кроме того, новый запрос объектов из базы данных не даст в результате текущих значений из базы данных. Запрос к базе лишь определит, какие сущности должны быть включены в результирующий набор. Если эти сущности уже кэшированы в DataContext, то будут возвращены именно эти кэшированные сущностные объекты.
Поэтому, чтобы действительно знать, какие значения ранее опрошенных сущностных объектов содержатся в базе данных, сущностные объекты должны сначала быть обновлены вызовом метода Refresh.
Таким образом, каждый из двух извлеченных сущностных объектов сначала обновляется, а затем на консоль выводится измененное свойство для доказательства, что изменения действительно были отменены.