Перенос изменений базы данных в транзакции (C#)
В этом руководстве рассматривается обновление, удаление и вставка пакетов данных. В этом руководстве мы узнаем, как транзакции базы данных позволяют выполнять пакетные изменения в виде атомарной операции, которая гарантирует, что все шаги будут успешными или все шаги завершаются сбоем.
Введение
Как мы видели, начиная с учебника Общие сведения о вставке, обновлении и удалении данных , GridView предоставляет встроенную поддержку редактирования и удаления на уровне строк. С помощью нескольких щелчков мыши можно создать полнофункционированный интерфейс изменения данных без написания строки кода, если вы будете довольствоваться редактированием и удалением для каждой строки. Однако в некоторых сценариях этого недостаточно, и необходимо предоставить пользователям возможность изменять или удалять пакет записей.
Например, большинство веб-клиентов электронной почты используют сетку для перечисления каждого сообщения, где каждая строка содержит флажок вместе со сведениями электронной почты (тема, отправитель и т. д.). Этот интерфейс позволяет пользователю удалить несколько сообщений, проверив их и нажав кнопку Удалить выбранные сообщения. Интерфейс пакетного редактирования идеально подходит в ситуациях, когда пользователи обычно редактируют множество различных записей. Вместо того чтобы заставлять пользователя нажать кнопку Изменить, внести изменения и нажать кнопку Обновить для каждой записи, которую необходимо изменить, интерфейс пакетного редактирования отрисовывает каждую строку со своим интерфейсом редактирования. Пользователь может быстро изменить набор строк, которые необходимо изменить, а затем сохранить эти изменения, нажав кнопку Обновить все. В этом наборе учебников мы рассмотрим создание интерфейсов для вставки, редактирования и удаления пакетов данных.
При выполнении пакетных операций важно определить, возможно ли, чтобы некоторые операции в пакете были успешными, а другие — сбоем. Рассмотрим интерфейс пакетного удаления. Что должно произойти, если первая выбранная запись успешно удалена, а вторая — неудачно, скажем, из-за нарушения ограничения внешнего ключа? Следует ли откатывать удаление первой записи или допустимо, чтобы первая запись оставалась удаленной?
Если требуется, чтобы пакетная операция рассматривалась как атомарная операция, при которой все шаги завершаются успешно или завершаются сбоем, необходимо расширить уровень доступа к данным, чтобы включить поддержку транзакций базы данных. Транзакции базы данных гарантируют атомарность для набора инструкций INSERT
, UPDATE
и DELETE
, выполняемых в рамках транзакции, и являются функцией, поддерживаемой большинством современных систем баз данных.
В этом руководстве мы рассмотрим, как расширить DAL для использования транзакций базы данных. В последующих руководствах рассматривается реализация веб-страниц для пакетной вставки, обновления и удаления интерфейсов. Приступим!
Примечание
При изменении данных в пакетной транзакции атомарность не всегда требуется. В некоторых сценариях может быть приемлемо, чтобы некоторые изменения данных были успешными, а другие — в том же пакете, например при удалении набора сообщений электронной почты из веб-клиента электронной почты. Если в середине процесса удаления возникает ошибка базы данных, вероятно, приемлемо, чтобы эти записи, обработанные без ошибок, оставались удаленными. В таких случаях DAL не требуется изменять для поддержки транзакций базы данных. Однако существуют и другие сценарии пакетных операций, в которых атомарность имеет жизненно важное значение. Когда клиент перемещает свои средства с одного банковского счета на другой, необходимо выполнить две операции: средства должны быть вычтены с первого счета, а затем добавлены на второй. Хотя банк может не возражать, чтобы первый шаг успешно, но второй шаг потерпел неудачу, его клиенты по понятным причинам будут расстроены. Я рекомендую вам ознакомиться с этим руководством и реализовать усовершенствования DAL для поддержки транзакций баз данных, даже если вы не планируете использовать их в пакетной вставке, обновлении и удалении интерфейсов, которые мы будем создавать в следующих трех учебниках.
Обзор транзакций
Большинство баз данных поддерживают транзакции, которые позволяют сгруппировать несколько команд базы данных в одну логическую единицу работы. Команды базы данных, составляющие транзакцию, гарантированно будут атомарными. Это означает, что либо все команды завершатся ошибкой, либо все будут успешными.
Как правило, транзакции реализуются с помощью инструкций SQL по следующему шаблону:
- Укажите начало транзакции.
- Выполните инструкции SQL, составляющие транзакцию.
- Если в одной из инструкций из шага 2 есть ошибка, откат транзакции.
- Если все инструкции из шага 2 выполняются без ошибок, зафиксируйте транзакцию.
Инструкции SQL, используемые для создания, фиксации и отката транзакции, можно вводить вручную при написании скриптов SQL или создании хранимых процедур либо программными средствами с помощью ADO.NET или классов в System.Transactions
пространстве имен. В этом руководстве мы рассмотрим управление транзакциями только с помощью ADO.NET. В следующем руководстве мы рассмотрим, как использовать хранимые процедуры на уровне доступа к данным. Затем мы рассмотрим инструкции SQL для создания, отката и фиксации транзакций.
Примечание
Класс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
SqlTransaction myTransaction = 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;
}
По умолчанию адаптеры 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.cs
(см. рис. 4). Этот файл будет содержать частичную реализацию ProductsTableAdapter
, которая включает методы для изменения данных с помощью транзакции.
Рис. 4. Добавление папки с именем TransactionSupport
и файла класса с именем ProductsTableAdapter.TransactionSupport.cs
Введите в файл следующий код ProductsTableAdapter.TransactionSupport.cs
:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace NorthwindTableAdapters
{
public partial class ProductsTableAdapter
{
private SqlTransaction _transaction;
private SqlTransaction Transaction
{
get
{
return this._transaction;
}
set
{
this._transaction = value;
}
}
public void BeginTransaction()
{
// Open the connection, if needed
if (this.Connection.State != ConnectionState.Open)
this.Connection.Open();
// Create the transaction and assign it to the Transaction property
this.Transaction = this.Connection.BeginTransaction();
// Attach the transaction to the Adapters
foreach (SqlCommand command in this.CommandCollection)
{
command.Transaction = this.Transaction;
}
this.Adapter.InsertCommand.Transaction = this.Transaction;
this.Adapter.UpdateCommand.Transaction = this.Transaction;
this.Adapter.DeleteCommand.Transaction = this.Transaction;
}
public void CommitTransaction()
{
// Commit the transaction
this.Transaction.Commit();
// Close the connection
this.Connection.Close();
}
public void RollbackTransaction()
{
// Rollback the transaction
this.Transaction.Rollback();
// Close the connection
this.Connection.Close();
}
}
}
Ключевое слово partial
в объявлении класса здесь указывает компилятору, что члены, добавленные в , должны быть добавлены в ProductsTableAdapter
класс в NorthwindTableAdapters
пространстве имен. Обратите внимание на инструкцию using 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 int UpdateWithTransaction(Northwind.ProductsDataTable dataTable)
{
this.BeginTransaction();
try
{
// Perform the update on the DataTable
int returnValue = this.Adapter.Update(dataTable);
// If we reach here, no errors, so commit the transaction
this.CommitTransaction();
return returnValue;
}
catch
{
// If we reach here, there was an error, so rollback the transaction
this.RollbackTransaction();
throw;
}
}
Добавьте метод в UpdateWithTransaction
класс с ProductsTableAdapter
помощью разделяемого класса в ProductsTableAdapter.TransactionSupport.cs
. Кроме того, этот метод можно добавить в класс уровня бизнес-логики ProductsBLL
с несколькими незначительными синтаксическими изменениями. А именно, ключевое слово это в this.BeginTransaction()
, this.CommitTransaction()
и this.RollbackTransaction()
необходимо заменить Adapter
на (напомним, что Adapter
это имя свойства в ProductsBLL
типе ProductsTableAdapter
).
Метод UpdateWithTransaction
использует шаблон пакетного обновления, но ряд вызовов DB-Direct также можно использовать в область транзакции, как показано в следующем методе. Метод DeleteProductsWithTransaction
принимает в качестве входных данных List<T>
тип int
, который является удаляемой ProductID
. Метод инициирует транзакцию через вызов BeginTransaction
, а затем в try
блоке выполняет итерацию по предоставленному списку, вызывая метод шаблона DB-Direct Delete
для каждого ProductID
значения. Если какой-либо из вызовов к завершается Delete
сбоем, управление передается блоку catch
, в котором выполняется откат транзакции, и повторно создается исключение. Если все вызовы будут Delete
выполнены успешно, транзакция фиксируется. Добавьте этот метод в ProductsBLL
класс .
public void DeleteProductsWithTransaction
(System.Collections.Generic.List<int> productIDs)
{
// Start the transaction
Adapter.BeginTransaction();
try
{
// Delete each product specified in the list
foreach (int productID in productIDs)
{
Adapter.Delete(productID);
}
// Commit the transaction
Adapter.CommitTransaction();
}
catch
{
// There was an error - rollback the transaction
Adapter.RollbackTransaction();
throw;
}
}
Применение транзакций в нескольких адаптерах 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 int UpdateWithTransaction(Northwind.ProductsDataTable products)
{
return Adapter.UpdateWithTransaction(products);
}
public void DeleteProductsWithTransaction
(System.Collections.Generic.List<int> productIDs)
{
// Start the transaction
Adapter.BeginTransaction();
try
{
// Delete each product specified in the list
foreach (int productID in productIDs)
Adapter.Delete(productID);
// Commit the transaction
Adapter.CommitTransaction();
}
catch
{
// There was an error - rollback the transaction
Adapter.RollbackTransaction();
throw;
}
}
Примечание
Эти методы не включают атрибут, назначенный 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. Рис. 5. Настройка ObjectDataSource для использования ProductsBLL
метода Classs 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 void RefreshGrid_Click(object sender, EventArgs e)
{
Products.DataBind();
}
protected void ModifyCategoriesWithTransaction_Click(object sender, EventArgs e)
{
// Get the set of products
ProductsBLL productsAPI = new ProductsBLL();
Northwind.ProductsDataTable products = productsAPI.GetProducts();
// Update each product's CategoryID
foreach (Northwind.ProductsRow product in products)
{
product.CategoryID = product.ProductID;
}
// Update the data using a transaction
productsAPI.UpdateWithTransaction(products);
// Refresh the Grid
Products.DataBind();
}
protected void ModifyCategoriesWithoutTransaction_Click(object sender, EventArgs e)
{
// Get the set of products
ProductsBLL productsAPI = new ProductsBLL();
Northwind.ProductsDataTable products = productsAPI.GetProducts();
// Update each product's CategoryID
foreach (Northwind.ProductsRow product in products)
{
product.CategoryID = product.ProductID;
}
// Update the data WITHOUT using a transaction
NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
new NorthwindTableAdapters.ProductsTableAdapter();
productsAdapter.Update(products);
// Refresh the Grid
Products.DataBind();
}
Обработчик событий 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 Database в .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.
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по