Перенос изменений базы данных в транзакции (C#)

Скотт Митчелл

Загрузить PDF-файл

В этом руководстве рассматривается обновление, удаление и вставка пакетов данных. В этом руководстве мы узнаем, как транзакции базы данных позволяют выполнять пакетные изменения в виде атомарной операции, которая гарантирует, что все шаги будут успешными или все шаги завершаются сбоем.

Введение

Как мы видели, начиная с учебника Общие сведения о вставке, обновлении и удалении данных , GridView предоставляет встроенную поддержку редактирования и удаления на уровне строк. С помощью нескольких щелчков мыши можно создать полнофункционированный интерфейс изменения данных без написания строки кода, если вы будете довольствоваться редактированием и удалением для каждой строки. Однако в некоторых сценариях этого недостаточно, и необходимо предоставить пользователям возможность изменять или удалять пакет записей.

Например, большинство веб-клиентов электронной почты используют сетку для перечисления каждого сообщения, где каждая строка содержит флажок вместе со сведениями электронной почты (тема, отправитель и т. д.). Этот интерфейс позволяет пользователю удалить несколько сообщений, проверив их и нажав кнопку Удалить выбранные сообщения. Интерфейс пакетного редактирования идеально подходит в ситуациях, когда пользователи обычно редактируют множество различных записей. Вместо того чтобы заставлять пользователя нажать кнопку Изменить, внести изменения и нажать кнопку Обновить для каждой записи, которую необходимо изменить, интерфейс пакетного редактирования отрисовывает каждую строку со своим интерфейсом редактирования. Пользователь может быстро изменить набор строк, которые необходимо изменить, а затем сохранить эти изменения, нажав кнопку Обновить все. В этом наборе учебников мы рассмотрим создание интерфейсов для вставки, редактирования и удаления пакетов данных.

При выполнении пакетных операций важно определить, возможно ли, чтобы некоторые операции в пакете были успешными, а другие — сбоем. Рассмотрим интерфейс пакетного удаления. Что должно произойти, если первая выбранная запись успешно удалена, а вторая — неудачно, скажем, из-за нарушения ограничения внешнего ключа? Следует ли откатывать удаление первой записи или допустимо, чтобы первая запись оставалась удаленной?

Если требуется, чтобы пакетная операция рассматривалась как атомарная операция, при которой все шаги завершаются успешно или завершаются сбоем, необходимо расширить уровень доступа к данным, чтобы включить поддержку транзакций базы данных. Транзакции базы данных гарантируют атомарность для набора инструкций INSERT, UPDATEи DELETE , выполняемых в рамках транзакции, и являются функцией, поддерживаемой большинством современных систем баз данных.

В этом руководстве мы рассмотрим, как расширить DAL для использования транзакций базы данных. В последующих руководствах рассматривается реализация веб-страниц для пакетной вставки, обновления и удаления интерфейсов. Приступим!

Примечание

При изменении данных в пакетной транзакции атомарность не всегда требуется. В некоторых сценариях может быть приемлемо, чтобы некоторые изменения данных были успешными, а другие — в том же пакете, например при удалении набора сообщений электронной почты из веб-клиента электронной почты. Если в середине процесса удаления возникает ошибка базы данных, вероятно, приемлемо, чтобы эти записи, обработанные без ошибок, оставались удаленными. В таких случаях DAL не требуется изменять для поддержки транзакций базы данных. Однако существуют и другие сценарии пакетных операций, в которых атомарность имеет жизненно важное значение. Когда клиент перемещает свои средства с одного банковского счета на другой, необходимо выполнить две операции: средства должны быть вычтены с первого счета, а затем добавлены на второй. Хотя банк может не возражать, чтобы первый шаг успешно, но второй шаг потерпел неудачу, его клиенты по понятным причинам будут расстроены. Я рекомендую вам ознакомиться с этим руководством и реализовать усовершенствования DAL для поддержки транзакций баз данных, даже если вы не планируете использовать их в пакетной вставке, обновлении и удалении интерфейсов, которые мы будем создавать в следующих трех учебниках.

Обзор транзакций

Большинство баз данных поддерживают транзакции, которые позволяют сгруппировать несколько команд базы данных в одну логическую единицу работы. Команды базы данных, составляющие транзакцию, гарантированно будут атомарными. Это означает, что либо все команды завершатся ошибкой, либо все будут успешными.

Как правило, транзакции реализуются с помощью инструкций SQL по следующему шаблону:

  1. Укажите начало транзакции.
  2. Выполните инструкции SQL, составляющие транзакцию.
  3. Если в одной из инструкций из шага 2 есть ошибка, откат транзакции.
  4. Если все инструкции из шага 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

Добавление страниц ASP.NET для учебников по SqlDataSource-Related

Рис. 1. Добавление страниц ASP.NET для учебников по SqlDataSource-Related

Как и в других папках, Default.aspx будет использовать SectionLevelTutorialListing.ascx пользовательский элемент управления для перечисления учебников в своем разделе. Поэтому добавьте этот пользовательский элемент управления в , 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 папки. Создайте вложенную папку в папке DALTransactionSupport и добавьте новый файл класса с именем ProductsTableAdapter.TransactionSupport.cs (см. рис. 4). Этот файл будет содержать частичную реализацию ProductsTableAdapter , которая включает методы для изменения данных с помощью транзакции.

Добавьте папку с именем TransactionSupport и файл класса с именем ProductsTableAdapter.TransactionSupport.cs

Рис. 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 и присоединяет транзакцию к внутренним SqlDataAdapterSqlCommand объектам . 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. Теперь в должно быть два новых метода: ProductsBLLUpdateWithTransaction, который вы только что добавили, и 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. Настройка ObjectDataSource для использования метода GetProducts класса ProductsBLL

Рис. 5. Рис. 5. Настройка ObjectDataSource для использования ProductsBLL метода Classs GetProducts (щелкните для просмотра полноразмерного изображения)

Задайте для Drop-Down Списки на вкладках UPDATE, INSERT и DELETE значение (Нет)

Рис. 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.

Страница содержит элемент управления GridView и веб-элементы управления

Рис. 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).

Продукты отображаются в элементе GridView с адаптируемым к страницам

Рис. 8. Продукты отображаются в элементе GridView для страничного просмотра (щелкните для просмотра полноразмерного изображения)

Переназначение категорий приводит к нарушению ограничения внешнего ключа

Рис. 9. Переназначение категорий приводит к нарушению ограничения внешнего ключа (щелкните для просмотра полноразмерного изображения)

Теперь нажмите кнопку Назад в браузере, а затем нажмите кнопку Обновить сетку. После обновления данных должны отобразиться те же выходные данные, что и на рис. 8. То есть, хотя некоторые продукты CategoryID были изменены на юридические значения и обновлены в базе данных, они были откатированы при нарушении ограничения внешнего ключа.

Теперь попробуйте нажать кнопку Изменить категории (БЕЗ ТРАНЗАКЦИИ). Это приведет к той же ошибке нарушения ограничения внешнего ключа (см. рис. 9), но на этот раз те продукты, значения которых CategoryID были изменены на юридическое значение, не будут откатированы. Нажмите в браузере кнопку Назад, а затем кнопку Обновить сетку. Как показано на рисунке CategoryID 10, из первых восьми продуктов были переназначены. Например, на рис. 8 чанг имеет CategoryID значение 1, но на рис. 10 оно было переназначен 2.

Некоторые продукты были обновлены значения CategoryID, в то время как другие не были

Рис. 10. Некоторые значения продуктов CategoryID были обновлены, а другие — нет (щелкните для просмотра полноразмерного изображения)

Сводка

По умолчанию методы TableAdapter не упаковывают выполняемые инструкции базы данных в область транзакции, но с небольшой работой мы можем добавить методы, которые будут создавать, фиксировать и откатывать транзакцию. В этом руководстве мы создали три таких метода в ProductsTableAdapter классе : BeginTransaction, CommitTransactionи RollbackTransaction. Мы узнали, как использовать эти методы вместе с блоком try...catch , чтобы сделать ряд операторов изменения данных атомарными. В частности, мы создали UpdateWithTransaction метод в ProductsTableAdapter, который использует шаблон пакетного обновления для выполнения необходимых изменений в строках предоставленного ProductsDataTableобъекта . Мы также добавили метод в класс в BLL, который принимает значение ProductIDList со значениями в качестве входных данных и вызывает метод Delete шаблона DB-Direct для каждого ProductID.ProductsBLLDeleteProductsWithTransaction Оба метода начинаются с создания транзакции, а затем выполнения инструкций изменения данных в блоке try...catch . Если возникает исключение, транзакция откатывается, в противном случае она фиксируется.

На шаге 5 показан эффект пакетных обновлений транзакций и пакетных обновлений, в которых не используется транзакция. В следующих трех руководствах мы будем опираться на основу, заложенную в этом руководстве, и создадим пользовательские интерфейсы для пакетных обновлений, удаления и вставки.

Счастливого программирования!

Дополнительные материалы

Дополнительные сведения о темах, рассмотренных в этом руководстве, см. в следующих ресурсах:

Об авторе

Скотт Митчелл( 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.