Поделиться через


Перенос изменений базы данных в транзакции (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 пространстве имен позволяет разработчикам программно упаковывать ряд инструкций в рамках транзакции и включать поддержку сложных транзакций, включающих несколько источников, таких как две разные базы данных или даже разнородные типы хранилищ данных, такие как база данных Microsoft 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 .

  • Default.aspx
  • Transactions.aspx
  • BatchUpdate.aspx
  • BatchDelete.aspx
  • BatchInsert.aspx

Добавление страниц ASP.NET для учебников, связанных с SqlDataSource

Рис. 1. Добавление страниц ASP.NET для учебников, связанных с SqlDataSource

Как и в других папках, Default.aspx используйте SectionLevelTutorialListing.ascx элемент управления пользователем для перечисления учебников в своем разделе. Поэтому добавьте этот элемент управления Default.aspx пользователем, перетащив его из Обозреватель решений в представление конструктора страницы.

Добавьте элемент управления user Control 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 состоит из DataTables и TableAdapters. DataTables хранит данные, пока tableAdapters предоставляют функциональные возможности для чтения данных из базы данных в DataTables, для обновления базы данных с изменениями, внесенными в DataTables, и т. д. Помните, что TableAdapters предоставляют два шаблона для обновления данных, которые я назвал пакетным обновлением и DB-Direct. С помощью шаблона пакетного обновления таблицаAdapter передает набор данных, DataTable или коллекцию DataRows. Эти данные перечисляются и для каждой вставленной, измененной или удаленной строки, InsertCommandUpdateCommandили DeleteCommand выполняется. С помощью шаблона DB-Direct таблицаAdapter вместо этого передает значения столбцов, необходимых для вставки, обновления или удаления одной записи. Затем метод шаблона DB Direct использует переданные значения для выполнения соответствующего InsertCommandUpdateCommandоператора или DeleteCommand инструкции.

Независимо от используемого шаблона обновления методы TableAdapters, созданные автоматически, не используют транзакции. По умолчанию каждая вставка, обновление или удаление, выполняемое TableAdapter, рассматривается как одна дискретная операция. Например, представьте, что шаблон DB-Direct используется некоторым кодом в BLL для вставки десяти записей в базу данных. Этот код вызывает метод TableAdapter Insert десять раз. Если первые пять вставок выполнены успешно, но шестая из них привела к исключению, первые пять вставленных записей останутся в базе данных. Аналогичным образом, если шаблон пакетного обновления используется для выполнения вставок, обновлений и удалений в вставленные, измененные и удаленные строки в DataTable, если первые несколько изменений выполнены успешно, но позже возникла ошибка, эти предыдущие изменения, завершенные, останутся в базе данных.

В некоторых сценариях мы хотим обеспечить атомарность в ряде изменений. Для этого необходимо вручную расширить tableAdapter, добавив новые методы, которые выполняют InsertCommandUpdateCommandи DeleteCommand под зонтиком транзакции. При создании уровня доступа к данным мы рассмотрели использование частичных классов для расширения функциональных возможностей DataTables в типизированном наборе данных. Этот метод также можно использовать с TableAdapters.

Типизированный набор Northwind.xsd данных находится в подпапке App_Code папки DAL . Создайте вложенную папку в папке DAL с именем TransactionSupport и добавьте новый файл класса с именем 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 свойству и присоединяет транзакцию к внутренним SqlDataAdapter объектам s SqlCommand . CommitTransaction и RollbackTransaction вызовите Transaction объекты Commit и Rollback методы соответственно перед закрытием внутреннего Connection объекта.

Шаг 3. Добавление методов для обновления и удаления данных под зонтиком транзакции

После завершения этих методов мы готовы добавить методы ProductsDataTable в или BLL, которые выполняют ряд команд под зонтиком транзакции. Следующий метод использует шаблон пакетного обновления для обновления экземпляра ProductsDataTable с помощью транзакции. Она запускает транзакцию путем вызова BeginTransaction метода, а затем использует try...catch блок для выдачи инструкций изменения данных. Если вызов метода Adapter объекта Update приводит к исключению, выполнение передастся 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 s для удаления. Метод инициирует транзакцию с помощью вызова BeginTransaction , а затем в try блоке выполняет итерацию по указанному списку, вызывая метод шаблона Delete DB-Direct для каждого 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;
    }
}

Применение транзакций между несколькими таблицамиAdapters

Код, связанный с транзакциями, рассмотренный в этом руководстве, позволяет использовать несколько инструкций для ProductsTableAdapter обработки как атомарной операции. Но что делать, если необходимо выполнить атомарную операцию нескольких изменений в разных таблицах базы данных? Например, при удалении категории может потребоваться переназначить текущие продукты на другую категорию. Эти два шага переназначения продуктов и удаление категории должны выполняться как атомарная операция. Но включает ProductsTableAdapter только методы для изменения таблицы и CategoriesTableAdapter только методы для изменения Products 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, который при щелчке переназначает значения продуктов CategoryID . В частности, переназначение категории будет выполняться таким образом, чтобы первые несколько продуктов были назначены допустимым CategoryID значением, а другие специально назначают несуществующее CategoryID значение. Если мы пытаемся обновить базу данных с продуктом, который CategoryID не соответствует существующей категории CategoryID, возникнет нарушение ограничения внешнего ключа, и будет вызвано исключение. В этом примере мы увидим, что при использовании транзакции исключение, вызванное нарушением ограничения внешнего ключа, приведет к откату предыдущих допустимых CategoryID изменений. Однако, если транзакция не используется, изменения в начальных категориях останутся.

Начните с открытия Transactions.aspx страницы в BatchData папке и перетащите GridView из панели элементов в конструктор. Задайте для него ID Products значение и из смарт-тега привязать его к новому объекту ObjectDataSource с именем ProductsDataSource. Настройте ObjectDataSource для извлечения данных из ProductsBLL метода класса GetProducts . Это будет только для чтения GridView, поэтому задайте раскрывающийся список на вкладках UPDATE, INSERT и DELETE (Нет) и нажмите кнопку "Готово".

Рис. 5. Настройка ObjectDataSource для использования метода GetProducts класса ProductsBLL

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

Задайте раскрывающимся спискам в вкладках UPDATE, INSERT и DELETE (Нет)

Рис. 6. Задайте раскрывающийся список в вкладках UPDATE, INSERT и DELETE (Нет) (Щелкните, чтобы просмотреть изображение полного размера)

После завершения работы мастера настройки источника данных Visual Studio создаст BoundFields и CheckBoxField для полей данных продукта. Удалите все эти поля, кроме , ProductNameи переименуйте ProductName CategoryName свойства BoundFields HeaderText в Product и Category соответственно.CategoryName CategoryIDProductID В смарт-теге проверьте параметр "Включить разбиение по страницам". После внесения этих изменений декларативная разметка 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 над GridView. Задайте для свойства text первой кнопки значение "Обновить сетку", второй — "Изменить категории" (WITH 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();
}

Обработчик событий кнопки Click обновления просто перенаправит данные в GridView, вызвав Products метод GridView DataBind .

Второй обработчик событий переназначает продукты CategoryID и использует новый метод транзакции из BLL для выполнения обновлений базы данных под зонтиком транзакции. Обратите внимание, что каждому продукту CategoryID произвольно присваивается то же значение, что и его ProductID. Это будет работать хорошо для первых немногих продуктов, так как эти продукты имеют ProductID значения, которые сопоставляются с допустимыми CategoryID . Но как только ProductID начало становится слишком большим, это совпадение совпадений ProductID и CategoryID s больше не применяется.

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

Чтобы продемонстрировать это поведение, посетите эту страницу через браузер. Первоначально вы увидите первую страницу данных, как показано на рис. 8. Затем нажмите кнопку "Изменить категории" (WITH TRANSACTION). Это приведет к обратной отправке и попытке обновить все значения продуктов CategoryID , но приведет к нарушению ограничения внешнего ключа (см. рис. 9).

Продукты отображаются в pageable GridView

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

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

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

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

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

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

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

Итоги

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

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

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

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

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

Об авторе

Скотт Митчелл, автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с технологиями Microsoft Web с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Сэмс Учит себя ASP.NET 2.0 в 24 часах. Он может быть достигнут в mitchell@4GuysFromRolla.com. или через его блог, который можно найти на http://ScottOnWriting.NET.

Особое спасибо

Эта серия учебников была проверена многими полезными рецензентами. Ведущие рецензенты для этого руководства были Дэйв Гарднер, Хилтон Giesenow и Тереса Мерфи. Хотите просмотреть мои предстоящие статьи MSDN? Если да, упадите меня линию в mitchell@4GuysFromRolla.com.