Транзакции

67

»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ

Раньше уже говорилось о том, что при отсутствии активной транзакции во время вызова метода 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.

Таким образом, каждый из двух извлеченных сущностных объектов сначала обновляется, а затем на консоль выводится измененное свойство для доказательства, что изменения действительно были отменены.

Пройди тесты
Лучший чат для C# программистов