Перенос изменений базы данных в транзакции (VB)
В этом руководстве рассматривается обновление, удаление и вставка пакетов данных. В этом руководстве мы узнаем, как транзакции базы данных позволяют выполнять пакетные изменения в виде атомарной операции, которая гарантирует, что все шаги будут успешными или все шаги завершаются сбоем.
Введение
Как мы видели, начиная с учебника Общие сведения о вставке, обновлении и удалении данных , GridView предоставляет встроенную поддержку редактирования и удаления на уровне строк. С помощью нескольких щелчков мыши можно создать полнофункционированный интерфейс изменения данных без написания строки кода, если вы будете довольствоваться редактированием и удалением для каждой строки. Однако в некоторых сценариях этого недостаточно, и необходимо предоставить пользователям возможность изменять или удалять пакет записей.
Например, большинство веб-клиентов электронной почты используют сетку для перечисления каждого сообщения, где каждая строка содержит флажок вместе со сведениями электронной почты (тема, отправитель и т. д.). Этот интерфейс позволяет пользователю удалить несколько сообщений, проверив их и нажав кнопку Удалить выбранные сообщения. Интерфейс пакетного редактирования идеально подходит в ситуациях, когда пользователи обычно редактируют множество различных записей. Вместо того чтобы заставлять пользователя нажать кнопку Изменить, внести изменения и нажать кнопку Обновить для каждой записи, которую необходимо изменить, интерфейс пакетного редактирования отрисовывает каждую строку со своим интерфейсом редактирования. Пользователь может быстро изменить набор строк, которые необходимо изменить, а затем сохранить эти изменения, нажав кнопку Обновить все. В этом наборе учебников мы рассмотрим создание интерфейсов для вставки, редактирования и удаления пакетов данных.
При выполнении пакетных операций важно определить, возможно ли, чтобы некоторые операции в пакете были успешными, а другие — сбоем. Рассмотрим интерфейс пакетного удаления. Что должно произойти, если первая выбранная запись успешно удалена, а вторая — неудачно, скажем, из-за нарушения ограничения внешнего ключа? Следует ли откатывать удаление первой записи или допустимо, чтобы первая запись оставалась удаленной?
Если требуется, чтобы пакетная операция рассматривалась как атомарная операция, при которой все шаги завершаются успешно или завершаются сбоем, необходимо расширить уровень доступа к данным, чтобы включить поддержку транзакций базы данных. Транзакции базы данных гарантируют атомарность для набора инструкций INSERT
, UPDATE
и DELETE
, выполняемых в рамках транзакции, и являются функцией, поддерживаемой большинством современных систем баз данных.
В этом руководстве мы рассмотрим, как расширить DAL для использования транзакций базы данных. В последующих руководствах рассматривается реализация веб-страниц для пакетной вставки, обновления и удаления интерфейсов. Приступим!
Примечание
При изменении данных в пакетной транзакции атомарность не всегда требуется. В некоторых сценариях может быть приемлемо, чтобы некоторые изменения данных были успешными, а другие — в том же пакете, например при удалении набора сообщений электронной почты из веб-клиента электронной почты. Если в середине процесса удаления возникает ошибка базы данных, вероятно, приемлемо, чтобы эти записи, обработанные без ошибок, оставались удаленными. В таких случаях DAL не требуется изменять для поддержки транзакций базы данных. Однако существуют и другие сценарии пакетных операций, в которых атомарность имеет жизненно важное значение. Когда клиент перемещает свои средства с одного банковского счета на другой, необходимо выполнить две операции: средства должны быть вычтены с первого счета, а затем добавлены на второй. Хотя банк может не возражать, чтобы первый шаг успешно, но второй шаг потерпел неудачу, его клиенты по понятным причинам будут расстроены. Я рекомендую вам ознакомиться с этим руководством и реализовать усовершенствования DAL для поддержки транзакций баз данных, даже если вы не планируете использовать их в пакетной вставке, обновлении и удалении интерфейсов, которые мы будем создавать в следующих трех учебниках.
Обзор транзакций
Большинство баз данных поддерживают транзакции, которые позволяют сгруппировать несколько команд базы данных в одну логическую единицу работы. Команды базы данных, составляющие транзакцию, гарантированно будут атомарными. Это означает, что либо все команды завершатся ошибкой, либо все будут успешными.
Как правило, транзакции реализуются с помощью инструкций SQL по следующему шаблону:
- Укажите начало транзакции.
- Выполните инструкции SQL, составляющие транзакцию.
- Если в одной из инструкций из шага 2 есть ошибка, откат транзакции.
- Если все инструкции из шага 2 выполняются без ошибок, зафиксируйте транзакцию.
Инструкции SQL, используемые для создания, фиксации и отката транзакции, можно вводить вручную при написании скриптов SQL или создании хранимых процедур либо программными средствами с помощью ADO.NET или классов в System.Transactions
пространстве имен. В этом руководстве мы рассмотрим управление транзакциями только с помощью ADO.NET. В следующем руководстве мы рассмотрим, как использовать хранимые процедуры на уровне доступа к данным. Затем мы рассмотрим инструкции SQL для создания, отката и фиксации транзакций. В то же время дополнительные сведения см. в статье Управление транзакциями в SQL Server хранимых процедурах.
Примечание
КлассTransactionScope
в System.Transactions
пространстве имен позволяет разработчикам программно упаковывать ряд инструкций в область транзакции и включает поддержку сложных транзакций, которые включают несколько источников, таких как две разные базы данных или даже разнородные типы хранилищ данных, такие как база данных SQL Server Майкрософт, база данных Oracle и веб-служба. Я решил использовать ADO.NET транзакции в этом руководстве вместо TransactionScope
класса , так как ADO.NET более специфична для транзакций базы данных и во многих случаях гораздо менее ресурсоемкая. Кроме того, в некоторых сценариях класс использует координатор распределенных TransactionScope
транзакций Майкрософт (MSDTC). Проблемы с конфигурацией, реализацией и производительностью, связанные с MSDTC, делают его довольно специализированным и сложным разделом и выходит за область этих учебников.
При работе с поставщиком SqlClient в ADO.NET транзакции инициируются посредством вызова SqlConnection
метода класса , BeginTransaction
который возвращает SqlTransaction
объект . Инструкции изменения данных, которые создают транзакцию, помещаются в try...catch
блок . Если в инструкции в блоке try
возникает ошибка, выполнение передается catch
в блок, где транзакцию можно откатить с помощью SqlTransaction
метода объекта Rollback
. Если все инструкции выполняются успешно, вызов SqlTransaction
метода объекта Commit
в конце try
блока фиксирует транзакцию. Этот шаблон иллюстрируется в следующем фрагменте кода.
' Create the SqlTransaction object
Dim myTransaction As SqlTransaction = SqlConnectionObject.BeginTransaction();
Try
'
' ... Perform the database transaction�s data modification statements...
'
' If we reach here, no errors, so commit the transaction
myTransaction.Commit()
Catch
' If we reach here, there was an error, so rollback the transaction
myTransaction.Rollback()
Throw
End Try
По умолчанию адаптеры TableAdapters в типизированном наборе данных не используют транзакции. Чтобы обеспечить поддержку транзакций, необходимо расширить классы TableAdapter, чтобы включить дополнительные методы, использующие приведенный выше шаблон для выполнения ряда инструкций изменения данных в область транзакции. На шаге 2 мы посмотрим, как использовать разделяемые классы для добавления этих методов.
Шаг 1. Создание веб-страниц для работы с пакетными данными
Прежде чем мы приступим к изучению того, как расширить DAL для поддержки транзакций базы данных, давайте сначала создадим веб-страницы ASP.NET, необходимые для работы с этим руководством и тремя приведенными ниже. Начните с добавления новой папки с именем BatchData
, а затем добавьте следующие ASP.NET страницы, связав каждую страницу со страницей Site.master
master.
Default.aspx
Transactions.aspx
BatchUpdate.aspx
BatchDelete.aspx
BatchInsert.aspx
Рис. 1. Добавление страниц ASP.NET для учебников по SqlDataSource-Related
Как и в других папках, Default.aspx
будет использовать SectionLevelTutorialListing.ascx
пользовательский элемент управления для перечисления учебников в своем разделе. Поэтому добавьте этот пользовательский элемент управления в , Default.aspx
перетащив его из Обозреватель решений в режим конструктора страницы.
Рис. 2. Добавление пользовательского SectionLevelTutorialListing.ascx
элемента управления в Default.aspx
(щелкните для просмотра полноразмерного изображения)
Наконец, добавьте эти четыре страницы в качестве записей в Web.sitemap
файл. В частности, добавьте следующую разметку после настройки карты <siteMapNode>
сайта :
<siteMapNode title="Working with Batched Data"
url="~/BatchData/Default.aspx"
description="Learn how to perform batch operations as opposed to
per-row operations.">
<siteMapNode title="Adding Support for Transactions"
url="~/BatchData/Transactions.aspx"
description="See how to extend the Data Access Layer to support
database transactions." />
<siteMapNode title="Batch Updating"
url="~/BatchData/BatchUpdate.aspx"
description="Build a batch updating interface, where each row in a
GridView is editable." />
<siteMapNode title="Batch Deleting"
url="~/BatchData/BatchDelete.aspx"
description="Explore how to create an interface for batch deleting
by adding a CheckBox to each GridView row." />
<siteMapNode title="Batch Inserting"
url="~/BatchData/BatchInsert.aspx"
description="Examine the steps needed to create a batch inserting
interface, where multiple records can be created at the
click of a button." />
</siteMapNode>
После обновления Web.sitemap
просмотрите веб-сайт учебников в браузере. Меню слева теперь содержит элементы для учебников по работе с пакетными данными.
Рис. 3. Схема сайта теперь включает записи для учебников по работе с пакетными данными
Шаг 2. Обновление уровня доступа к данным для поддержки транзакций базы данных
Как мы уже говорили в первом руководстве Создание уровня доступа к данным, типизированный набор данных в DAL состоит из таблиц Данных и Адаптеров таблиц. Таблицы DataTable хранят данные, а TableAdapters предоставляют функциональные возможности для чтения данных из базы данных в таблицы DataTables, обновления базы данных с учетом изменений, внесенных в таблицы DataTables, и т. д. Напомним, что TableAdapters предоставляют два шаблона обновления данных, которые я назвал пакетным обновлением и DB-Direct. При использовании шаблона пакетного обновления TableAdapter передается набор данных, таблица данных или коллекция DataRows. Эти данные перечисляются, и для каждой вставленной, измененной или удаленной строки InsertCommand
выполняется , UpdateCommand
или DeleteCommand
. В шаблоне DB-Direct tableAdapter вместо этого передаются значения столбцов, необходимых для вставки, обновления или удаления одной записи. Затем метод шаблона DB Direct использует эти переданные значения для выполнения соответствующей InsertCommand
инструкции , UpdateCommand
или DeleteCommand
.
Независимо от используемого шаблона обновления автоматически созданные методы TableAdapters не используют транзакции. По умолчанию каждая вставка, обновление или удаление, выполняемая TableAdapter, рассматривается как одна дискретная операция. Например, представьте, что шаблон DB-Direct используется в коде BLL для вставки десяти записей в базу данных. Этот код будет вызывать метод TableAdapter Insert
10 раз. Если первые пять операций вставки будут выполнены успешно, а шестая привела к исключению, первые пять вставленных записей останутся в базе данных. Аналогичным образом, если шаблон пакетного обновления используется для вставки, обновления и удаления в вставленных, измененных и удаленных строках в dataTable, если первые несколько изменений были выполнены успешно, но в одном из них возникла ошибка, эти ранее выполненные изменения останутся в базе данных.
В некоторых сценариях мы хотим обеспечить атомарность в ряде изменений. Для этого необходимо вручную расширить TableAdapter, добавив новые методы, которые выполняют InsertCommand
, UpdateCommand
и DeleteCommand
в рамках транзакции. В разделе Создание уровня доступа к данным мы рассмотрели использование разделяемых классов для расширения функциональных возможностей DataTables в типизированном наборе данных. Этот метод также можно использовать с TableAdapters.
Типизированный набор Northwind.xsd
данных находится во вложенной App_Code
папке DAL
папки. Создайте вложенную папку в папке DAL
TransactionSupport
и добавьте новый файл класса с именем ProductsTableAdapter.TransactionSupport.vb
(см. рис. 4). Этот файл будет содержать частичную реализацию ProductsTableAdapter
, которая включает методы для изменения данных с помощью транзакции.
Рис. 4. Добавление папки с именем TransactionSupport
и файла класса с именем ProductsTableAdapter.TransactionSupport.vb
Введите в файл следующий код ProductsTableAdapter.TransactionSupport.vb
:
Imports System.Data
Imports System.Data.SqlClient
Namespace NorthwindTableAdapters
Partial Public Class ProductsTableAdapter
Private _transaction As SqlTransaction
Private Property Transaction() As SqlTransaction
Get
Return Me._transaction
End Get
Set(ByVal Value As SqlTransaction)
Me._transaction = Value
End Set
End Property
Public Sub BeginTransaction()
' Open the connection, if needed
If Me.Connection.State <> ConnectionState.Open Then
Me.Connection.Open()
End If
' Create the transaction and assign it to the Transaction property
Me.Transaction = Me.Connection.BeginTransaction()
' Attach the transaction to the Adapters
For Each command As SqlCommand In Me.CommandCollection
command.Transaction = Me.Transaction
Next
Me.Adapter.InsertCommand.Transaction = Me.Transaction
Me.Adapter.UpdateCommand.Transaction = Me.Transaction
Me.Adapter.DeleteCommand.Transaction = Me.Transaction
End Sub
Public Sub CommitTransaction()
' Commit the transaction
Me.Transaction.Commit()
' Close the connection
Me.Connection.Close()
End Sub
Public Sub RollbackTransaction()
' Rollback the transaction
Me.Transaction.Rollback()
' Close the connection
Me.Connection.Close()
End Sub
End Class
End Namespace
Ключевое слово Partial
в объявлении класса здесь указывает компилятору, что члены, добавленные в , должны быть добавлены в ProductsTableAdapter
класс в NorthwindTableAdapters
пространстве имен. Обратите внимание на инструкцию Imports System.Data.SqlClient
в верхней части файла. Так как TableAdapter был настроен для использования поставщика SqlClient, он внутренне использует SqlDataAdapter
объект для выполнения своих команд в базе данных. Следовательно, необходимо использовать класс , SqlTransaction
чтобы начать транзакцию, а затем зафиксировать ее или откатить. Если вы используете хранилище данных, отличное от Microsoft SQL Server, вам потребуется соответствующий поставщик.
Эти методы предоставляют стандартные блоки, необходимые для запуска, отката и фиксации транзакции. Они помечаются Public
, что позволяет использовать их из ProductsTableAdapter
, из другого класса в DAL или из другого слоя в архитектуре, например BLL. BeginTransaction
открывает внутренний SqlConnection
объект TableAdapter (при необходимости), начинает транзакцию и назначает ее свойству Transaction
и присоединяет транзакцию к внутренним SqlDataAdapter
SqlCommand
объектам . CommitTransaction
и RollbackTransaction
вызывают Transaction
методы объекта Commit
и Rollback
соответственно, прежде чем закрывать внутренний Connection
объект.
Шаг 3. Добавление методов для обновления и удаления данных в рамках транзакции
После завершения этих методов мы готовы добавить методы в ProductsDataTable
или BLL, которые выполняют ряд команд в рамках транзакции. Следующий метод использует шаблон пакетного обновления для обновления экземпляра ProductsDataTable
с помощью транзакции. Он запускает транзакцию путем вызова BeginTransaction
метода , а затем использует Try...Catch
блок для выдачи инструкций изменения данных. Если вызов метода объекта Update
приводит к Adapter
исключению, выполнение будет перенесено catch
в блок, где будет откат транзакции, и исключение будет создано повторно. Помните, что Update
метод реализует шаблон пакетного обновления путем перечисления строк предоставленного ProductsDataTable
и выполнения необходимых InsertCommand
, UpdateCommand
и DeleteCommand
s. Если одна из этих команд приводит к ошибке, транзакция откатывается, отменяя предыдущие изменения, внесенные в течение времени существования транзакции. Update
Если инструкция завершается без ошибок, транзакция фиксируется полностью.
Public Function UpdateWithTransaction _
(ByVal dataTable As Northwind.ProductsDataTable) As Integer
Me.BeginTransaction()
Try
' Perform the update on the DataTable
Dim returnValue As Integer = Me.Adapter.Update(dataTable)
' If we reach here, no errors, so commit the transaction
Me.CommitTransaction()
Return returnValue
Catch
' If we reach here, there was an error, so rollback the transaction
Me.RollbackTransaction()
Throw
End Try
End Function
Добавьте метод в UpdateWithTransaction
класс с ProductsTableAdapter
помощью разделяемого класса в ProductsTableAdapter.TransactionSupport.vb
. Кроме того, этот метод можно добавить в класс уровня бизнес-логики ProductsBLL
с несколькими незначительными синтаксическими изменениями. А именно, ключевое слово в , и необходимо заменить Adapter
на (напомним, что Adapter
это имя свойства в ProductsBLL
типе ProductsTableAdapter
).Me.RollbackTransaction()
Me.CommitTransaction()
Me.BeginTransaction()
Me
Метод UpdateWithTransaction
использует шаблон пакетного обновления, но ряд вызовов DB-Direct также можно использовать в область транзакции, как показано в следующем методе. Метод DeleteProductsWithTransaction
принимает в качестве входных данных List(Of T)
тип Integer
, который является удаляемой ProductID
. Метод инициирует транзакцию через вызов BeginTransaction
, а затем в Try
блоке выполняет итерацию по предоставленному списку, вызывая метод шаблона DB-Direct Delete
для каждого ProductID
значения. Если какой-либо из вызовов к завершается Delete
сбоем, управление передается блоку Catch
, в котором выполняется откат транзакции, и повторно создается исключение. Если все вызовы будут Delete
выполнены успешно, транзакция фиксируется. Добавьте этот метод в ProductsBLL
класс .
Public Sub DeleteProductsWithTransaction _
(ByVal productIDs As System.Collections.Generic.List(Of Integer))
' Start the transaction
Adapter.BeginTransaction()
Try
' Delete each product specified in the list
For Each productID As Integer In productIDs
Adapter.Delete(productID)
Next
' Commit the transaction
Adapter.CommitTransaction()
Catch
' There was an error - rollback the transaction
Adapter.RollbackTransaction()
Throw
End Try
End Sub
Применение транзакций в нескольких адаптерах tableAdapter
Код, связанный с транзакциями, рассмотренный в этом руководстве ProductsTableAdapter
, позволяет обрабатывать несколько инструкций для объекта как атомарную операцию. Но что делать, если необходимо выполнить несколько изменений в разных таблицах базы данных атомарно? Например, при удалении категории может потребоваться сначала переназначить ее текущие продукты какой-то другой категории. Эти два шага для переназначения продуктов и удаления категории должны выполняться как атомарная операция. ProductsTableAdapter
Но включает только методы для изменения таблицы, Products
а CategoriesTableAdapter
включает только методы для изменения Categories
таблицы. Итак, как транзакция может охватывать оба объекта TableAdapters?
Один из вариантов — добавить метод в CategoriesTableAdapter
именованный DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)
объект и вызвать хранимую процедуру, которая переназначает продукты и удаляет категорию в область транзакции, определенной в хранимой процедуре. В следующем руководстве мы рассмотрим, как начинать, фиксировать и откатывать транзакции в хранимых процедурах.
Другой вариант — создать вспомогательный класс в DAL, содержащий DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)
метод . Этот метод создаст экземпляр CategoriesTableAdapter
и , а затем присвоит ProductsTableAdapter
этим двум свойствам TableAdapters Connection
один и тот же SqlConnection
экземпляр. На этом этапе любой из двух TableAdapters инициирует транзакцию с вызовом BeginTransaction
метода . Методы TableAdapters для переназначения продуктов и удаления категории будут вызываться в блоке Try...Catch
с фиксацией транзакции или откатом при необходимости.
Шаг 4. Добавление метода наUpdateWithTransaction
уровень бизнес-логики
На шаге 3 мы добавили UpdateWithTransaction
метод в ProductsTableAdapter
в DAL. Необходимо добавить соответствующий метод в BLL. Хотя уровень презентации может вызывать непосредственно DAL для вызова UpdateWithTransaction
метода , в этих руководствах мы стремились определить многоуровневую архитектуру, которая изолирует DAL от слоя презентации. Поэтому мы должны продолжать этот подход.
ProductsBLL
Откройте файл класса и добавьте метод с именем UpdateWithTransaction
, который просто вызывает соответствующий метод DAL. Теперь в должно быть два новых метода: ProductsBLL
UpdateWithTransaction
, который вы только что добавили, и DeleteProductsWithTransaction
, который был добавлен на шаге 3.
Public Function UpdateWithTransaction _
(ByVal products As Northwind.ProductsDataTable) As Integer
Return Adapter.UpdateWithTransaction(products)
End Function
Public Sub DeleteProductsWithTransaction _
(ByVal productIDs As System.Collections.Generic.List(Of Integer))
' Start the transaction
Adapter.BeginTransaction()
Try
' Delete each product specified in the list
For Each productID As Integer In productIDs
Adapter.Delete(productID)
Next
' Commit the transaction
Adapter.CommitTransaction()
Catch
' There was an error - rollback the transaction
Adapter.RollbackTransaction()
Throw
End Try
End Sub
Примечание
Эти методы не включают атрибут, назначенный DataObjectMethodAttribute
большинству других методов в ProductsBLL
классе , так как мы будем вызывать эти методы непосредственно из классов кода программной части ASP.NET страниц. Помните, что DataObjectMethodAttribute
используется для пометки методов, которые должны отображаться в мастере настройки источника данных ObjectDataSource и на какой вкладке (SELECT, UPDATE, INSERT или DELETE). Так как в GridView отсутствует встроенная поддержка пакетного редактирования или удаления, нам придется вызывать эти методы программным способом, а не использовать декларативный подход без написания кода.
Шаг 5. Атомарное обновление данных базы данных из уровня представления
Чтобы проиллюстрировать влияние транзакции при обновлении пакета записей, давайте создадим пользовательский интерфейс, который перечисляет все продукты в GridView и включает элемент управления Button Web, который при щелчке переназначает значения продуктов CategoryID
. В частности, переназначение категории будет выполняться таким образом, что первым нескольким продуктам присваивается допустимое CategoryID
значение, а другим — несуществующее CategoryID
значение. Если мы попытаемся обновить базу данных с помощью продукта, который CategoryID
не соответствует существующей категории CategoryID
, произойдет нарушение ограничения внешнего ключа и возникнет исключение. В этом примере мы увидим, что при использовании транзакции исключение, вызванное нарушением ограничения внешнего ключа, приведет к откату предыдущих допустимых CategoryID
изменений. Однако если транзакция не используется, изменения в исходных категориях останутся.
Начните с открытия Transactions.aspx
страницы в папке BatchData
и перетащите GridView с панели элементов на Designer. Присвойте ему ID
значение Products
и из смарт-тега привяжите его к новому объекту ObjectDataSource с именем ProductsDataSource
. Настройте ObjectDataSource для извлечения своих данных из ProductsBLL
метода класса .GetProducts
Это будет gridView только для чтения, поэтому установите для раскрывающихся списков на вкладках UPDATE, INSERT и DELETE значение (Нет) и нажмите кнопку Готово.
Рис. 5. Настройка ObjectDataSource для использования ProductsBLL
метода Класса GetProducts
(щелкните для просмотра полноразмерного изображения)
Рис. 6. Задайте для Drop-Down Списки на вкладках UPDATE, INSERT и DELETE значение (Нет) (щелкните для просмотра полноразмерного изображения)
После завершения работы мастера настройки источника данных Visual Studio создаст BoundFields и CheckBoxField для полей данных продукта. Удалите все эти поля, ProductID
за исключением , ProductName
, CategoryID
и CategoryName
и переименуйте ProductName
свойства и CategoryName
BoundFields HeaderText
в Product и Category соответственно. В смарт-теге проверка параметр Включить разбиение по страницам. После внесения этих изменений декларативная разметка GridView и ObjectDataSource должна выглядеть следующим образом:
<asp:GridView ID="Products" runat="server" AllowPaging="True"
AutoGenerateColumns="False" DataKeyNames="ProductID"
DataSourceID="ProductsDataSource">
<Columns>
<asp:BoundField DataField="ProductID" HeaderText="ProductID"
InsertVisible="False" ReadOnly="True"
SortExpression="ProductID" />
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="CategoryID" HeaderText="CategoryID"
SortExpression="CategoryID" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
SortExpression="CategoryName" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProducts" TypeName="ProductsBLL">
</asp:ObjectDataSource>
Затем добавьте три элемента управления Button Web над GridView. Задайте для первого свойства Text элемента Button значение Обновить сетку, для второго — значение Modify Categories (WITH TRANSACTION), а для третьего — значение Modify Categories (WITHOUT TRANSACTION).
<p>
<asp:Button ID="RefreshGrid" runat="server" Text="Refresh Grid" />
</p>
<p>
<asp:Button ID="ModifyCategoriesWithTransaction" runat="server"
Text="Modify Categories (WITH TRANSACTION)" />
</p>
<p>
<asp:Button ID="ModifyCategoriesWithoutTransaction" runat="server"
Text="Modify Categories (WITHOUT TRANSACTION)" />
</p>
На этом этапе представление конструктора в Visual Studio должно выглядеть так, как на снимке экрана, показанном на рисунке 7.
Рис. 7. Страница содержит элемент управления GridView и веб-элементы управления "Три кнопки" (щелкните для просмотра полноразмерного изображения)
Создайте обработчики событий для каждого из трех событий Button Click
и используйте следующий код:
Protected Sub RefreshGrid_Click _
(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles RefreshGrid.Click
Products.DataBind()
End Sub
Protected Sub ModifyCategoriesWithTransaction_Click _
(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles ModifyCategoriesWithTransaction.Click
' Get the set of products
Dim productsAPI As New ProductsBLL()
Dim productsData As Northwind.ProductsDataTable = productsAPI.GetProducts()
' Update each product's CategoryID
For Each product As Northwind.ProductsRow In productsData
product.CategoryID = product.ProductID
Next
' Update the data using a transaction
productsAPI.UpdateWithTransaction(productsData)
' Refresh the Grid
Products.DataBind()
End Sub
Protected Sub ModifyCategoriesWithoutTransaction_Click _
(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles ModifyCategoriesWithoutTransaction.Click
' Get the set of products
Dim productsAPI As New ProductsBLL()
Dim productsData As Northwind.ProductsDataTable = productsAPI.GetProducts()
' Update each product's CategoryID
For Each product As Northwind.ProductsRow In productsData
product.CategoryID = product.ProductID
Next
' Update the data WITHOUT using a transaction
Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
productsAdapter.Update(productsData)
' Refresh the Grid
Products.DataBind()
End Sub
Обработчик событий refresh Button Click
просто повторно привязает данные к GridView, вызывая Products
метод GridView DataBind
.
Второй обработчик событий переназначает продукты CategoryID
и использует новый метод транзакции из BLL для выполнения обновлений базы данных в рамках транзакции. Обратите внимание, что каждому продукту CategoryID
произвольно присваивается то же значение, что и его ProductID
. Это будет нормально работать для первых нескольких продуктов, так как эти продукты имеют ProductID
значения, которые сопоставляются с допустимыми CategoryID
. Но как только s ProductID
начинают получать слишком большой, это случайное перекрытие ProductID
s и CategoryID
s больше не применяется.
Третий Click
обработчик событий обновляет продукты CategoryID
таким же образом, но отправляет обновление в базу данных с помощью ProductsTableAdapter
метода s по умолчанию Update
. Этот Update
метод не заключает в оболочку последовательность команд в транзакции, поэтому эти изменения вносятся до первой ошибки нарушения ограничения внешнего ключа.
Чтобы продемонстрировать это поведение, посетите эту страницу в браузере. Изначально должна отобразиться первая страница данных, как показано на рис. 8. Затем нажмите кнопку Изменить категории (WITH TRANSACTION). Это приведет к обратной отправке и попытке обновить все значения продуктов CategoryID
, но приведет к нарушению ограничения внешнего ключа (см. рис. 9).
Рис. 8. Продукты отображаются в элементе GridView для страничного просмотра (щелкните для просмотра полноразмерного изображения)
Рис. 9. Переназначение категорий приводит к нарушению ограничения внешнего ключа (щелкните для просмотра полноразмерного изображения)
Теперь нажмите кнопку Назад в браузере, а затем нажмите кнопку Обновить сетку. После обновления данных должны отобразиться те же выходные данные, что и на рис. 8. То есть, хотя некоторые продукты CategoryID
были изменены на юридические значения и обновлены в базе данных, они были откатированы при нарушении ограничения внешнего ключа.
Теперь попробуйте нажать кнопку Изменить категории (БЕЗ ТРАНЗАКЦИИ). Это приведет к той же ошибке нарушения ограничения внешнего ключа (см. рис. 9), но на этот раз те продукты, значения которых CategoryID
были изменены на юридическое значение, не будут откатированы. Нажмите в браузере кнопку Назад, а затем кнопку Обновить сетку. Как показано на рисунке CategoryID
10, из первых восьми продуктов были переназначены. Например, на рис. 8 чанг имеет CategoryID
значение 1, но на рис. 10 оно было переназначен 2.
Рис. 10. Некоторые значения продуктов CategoryID
были обновлены, а другие — нет (щелкните для просмотра полноразмерного изображения)
Сводка
По умолчанию методы TableAdapter не переносят выполняемые инструкции базы данных в область транзакции, но с небольшой работой можно добавить методы, которые будут создавать, фиксировать и откатывать транзакцию. В этом руководстве мы создали три таких метода в ProductsTableAdapter
классе : BeginTransaction
, CommitTransaction
и RollbackTransaction
. Мы узнали, как использовать эти методы вместе с блоком Try...Catch
, чтобы сделать ряд операторов изменения данных атомарными. В частности, мы создали UpdateWithTransaction
метод в ProductsTableAdapter
, который использует шаблон пакетного обновления для выполнения необходимых изменений в строках предоставленного ProductsDataTable
. Мы также добавили метод в класс в BLL, который принимает в качестве входных ProductID
данных объект со значениями List
и вызывает метод Delete
шаблона DB-Direct для каждого ProductID
.ProductsBLL
DeleteProductsWithTransaction
Оба метода начинаются с создания транзакции и последующего выполнения инструкций изменения данных в блоке Try...Catch
. При возникновении исключения транзакция откатывается, в противном случае она фиксируется.
На шаге 5 показано влияние транзакционных пакетных обновлений и пакетных обновлений, которые не используют транзакцию. В следующих трех руководствах мы будем опираться на основы, заложенные в этом руководстве, и создавать пользовательские интерфейсы для выполнения пакетных обновлений, удалений и вставок.
Счастливое программирование!
Дополнительные материалы
Дополнительные сведения по темам, рассматриваемым в этом руководстве, см. в следующих ресурсах:
- Простые транзакции:
System.Transactions
- TransactionScope и DataAdapters
- Использование транзакций базы данных Oracle в .NET
Об авторе
Скотт Митчелл (Scott Mitchell), автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с Веб-технологиями Майкрософт с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams Teach Yourself ASP.NET 2.0 в 24 часа. Его можно связать по адресу mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET.
Отдельная благодарность
Эта серия учебников была проверена многими полезными рецензентами. Ведущие рецензенты этого руководства : Дэйв Гарднер, Хилтон Гизеноу и Тереса Мерфи. Хотите ознакомиться с моими предстоящими статьями MSDN? Если да, опустите мне строку в mitchell@4GuysFromRolla.com.