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


Пакетная вставка (C#)

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

Скачать в формате PDF

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

Введение

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

Эта концепция также может применяться при добавлении записей. Представьте себе, что здесь в Northwind Traders мы обычно получаем поставки от поставщиков, которые содержат ряд продуктов для определенной категории. Например, мы можем получить поставку шести различных чайных и кофейных изделий от Токийских торговцев. Если пользователь вводит шесть продуктов одновременно с помощью элемента управления DetailsView, им придется выбрать множество одинаковых значений снова и снова: им потребуется выбрать одну и ту же категорию (напитки), тот же поставщик (Токио Traders), то же самое прекращенное значение (False) и те же единицы по значению заказа (0). Эта повторяющаяся запись данных не только занимает много времени, но и подвержена ошибкам.

С небольшой работой мы можем создать пакетный интерфейс вставки, позволяющий пользователю выбрать поставщика и категорию один раз, ввести ряд имен продуктов и цен на единицы, а затем нажать кнопку, чтобы добавить новые продукты в базу данных (см. рис. 1). По мере добавления каждого продукта его поля данных ProductName и UnitPrice получают значения, введенные в TextBoxes, а его значения CategoryID и SupplierID присваиваются значениям из DropDownLists в верхней части формы. Значения Discontinued и UnitsOnOrder установлены в жестко заданные false и 0 соответственно.

Интерфейс пакетной вставки

Рис. 1. Интерфейс для пакетной вставки (Щелкните, чтобы просмотреть изображение в полном размере)

В этом руководстве мы создадим страницу, которая реализует интерфейс пакетной вставки, показанный на рис. 1. Как и в предыдущих двух учебниках, мы поместим вставки в область действия транзакции, чтобы обеспечить атомарность. Давайте приступим!

Шаг 1. Создание интерфейса отображения

Это руководство будет состоять из одной страницы, разделенной на два региона: область отображения и область вставки. Интерфейс отображения, который мы создадим на этом шаге, отображает товары в GridView и включает кнопку "Обработка отправки товара". При нажатии этой кнопки интерфейс отображения заменяется интерфейсом вставки, который показан на рис. 1. Интерфейс отображения возвращается после нажатия кнопки "Добавить продукты из отправки" или "Отмена". Мы создадим интерфейс вставки на шаге 2.

При создании страницы с двумя интерфейсами только один из которых отображается одновременно, каждый интерфейс обычно помещается в веб-элемент управления Panel, который служит контейнером для других элементов управления. Таким образом, наша страница будет содержать два элемента управления "Панель": по одному для каждого интерфейса.

Начните с открытия BatchInsert.aspx страницы в BatchData папке и перетащите панель из панели инструментов в дизайнер (см. рисунок 2). Задайте для свойства панели ID значение DisplayInterface. При добавлении панели в конструктор её свойства Height и Width имеют значения 50 пикселей и 125 пикселей соответственно. Удалите эти значения свойств из окна "Свойства".

Перетащите элемент из панели инструментов в конструктор

Рис. 2. Перетащите панель из Панели инструментов в конструктор (щелкните, чтобы просмотреть полноразмерное изображение)

Затем перетащите элемент управления Button и GridView в панель. Задайте для свойства Button ID значение ProcessShipment и свойство Text значение Process Product Shipment. Задайте свойству ID элемента управления GridView значение ProductsGrid, а затем из его Smart Tag следует привязать его к новому ObjectDataSource с именем ProductsDataSource. Настройте ObjectDataSource для извлечения данных из ProductsBLL метода класса GetProducts . Так как этот GridView используется только для отображения данных, установите раскрывающиеся списки на вкладках UPDATE, INSERT и DELETE в положение (Нет). Нажмите кнопку "Готово", чтобы завершить настройку источника данных.

Отображение данных, возвращаемых из метода GetProducts класса ProductsBLL

Рис. 3. Отображение данных, возвращаемых из ProductsBLL метода класса GetProducts (щелкните, чтобы просмотреть изображение полного размера)

Установите списки Drop-Down в вкладках UPDATE, INSERT и DELETE на (Нет)

Рис. 4: Установите списки Drop-Down в вкладках UPDATE, INSERT и DELETE на (Нет) (Щелкните, чтобы просмотреть изображение в полном размере)

После завершения мастера ObjectDataSource Visual Studio добавит BoundFields и CheckBoxField для полей данных продукта. Удалите все, кроме полей ProductName, CategoryName, SupplierName, UnitPrice, и Discontinued. Вы можете сделать любые эстетические настройки. Я решил отформатировать UnitPrice поле как значение валюты, переупорядочение полей и переименовать несколько значений полей HeaderText . Также настройте GridView для включения поддержки разбиения по страницам и сортировки, установив флажки "Включить разбиение по страницам" и "Включить сортировку" в смарт-теге GridView.

После добавления элементов управления Panel, Button, GridView и ObjectDataSource и настройки полей GridView декларативная разметка страницы должна выглядеть следующим образом:

<asp:Panel ID="DisplayInterface" runat="server">
    <p>
        <asp:Button ID="ProcessShipment" runat="server" 
            Text="Process Product Shipment" /> 
    </p>
    <asp:GridView ID="ProductsGrid" runat="server" AllowPaging="True" 
        AllowSorting="True" AutoGenerateColumns="False" 
        DataKeyNames="ProductID" DataSourceID="ProductsDataSource">
        <Columns>
            <asp:BoundField DataField="ProductName" HeaderText="Product" 
                SortExpression="ProductName" />
            <asp:BoundField DataField="CategoryName" HeaderText="Category" 
                ReadOnly="True" SortExpression="CategoryName" />
            <asp:BoundField DataField="SupplierName" HeaderText="Supplier" 
                ReadOnly="True" SortExpression="SupplierName" />
            <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" 
                HeaderText="Price" HtmlEncode="False" 
                SortExpression="UnitPrice">
                <ItemStyle HorizontalAlign="Right" />
            </asp:BoundField>
            <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" 
                SortExpression="Discontinued">
                <ItemStyle HorizontalAlign="Center" />
            </asp:CheckBoxField>
        </Columns>
    </asp:GridView>
    <asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
        OldValuesParameterFormatString="original_{0}"
        SelectMethod="GetProducts" TypeName="ProductsBLL">
    </asp:ObjectDataSource>
</asp:Panel>

Обратите внимание, что разметка для Button и GridView отображается в открывающих и закрывающих <asp:Panel> тегах. Так как эти элементы управления находятся на DisplayInterface панели, их можно скрыть, просто установив для свойства Панели Visible значение false. Шаг 3 позволяет программно изменить свойство Панели Visible в ответ на кнопку, чтобы отобразить один интерфейс при скрытии другого.

Ознакомьтесь с нашим прогрессом в браузере. Как показано на рисунке 5, вы увидите кнопку 'Отправка продукта в процессе' над GridView, которая перечисляет продукты по десять за раз.

GridView содержит список продуктов и предложений сортировки и разбиения по страницам

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

Шаг 2. Создание интерфейса вставки

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

Начните с перетаскивания панели из панели в конструктор, поместив ее под существующей DisplayInterface панелью. Задайте свойству ID этой недавно добавленной панели значение InsertingInterface, а ее свойству Visible задайте значение false. Мы добавим код, который задает свойство InsertingInterface панели Visible в true в шаге 3. Также очистите значения свойств Height и Width панели.

Далее необходимо создать интерфейс вставки, который был показан на рис. 1. Этот интерфейс можно создать с помощью различных методов HTML, но мы будем использовать достаточно простой: таблицу из четырех столбцов и семи строк.

Замечание

При вводе разметки для ЭЛЕМЕНТОВ HTML <table> я предпочитаю использовать представление источника. Хотя в Visual Studio есть средства для добавления <table> элементов через конструктор, конструктор слишком склонен внедрять ненужные параметры в style разметку. После создания разметки <table> я обычно возвращаюсь в Конструктор схем, чтобы добавить веб-элементы управления и задать их свойства. При создании таблиц с предварительно определенными столбцами и строками я предпочитаю использовать статический HTML, а не веб-элемент управления "Таблица" , так как к любым веб-элементам управления, размещенным в веб-элементе управления "Таблица", можно получить доступ только с помощью FindControl("controlID") шаблона. Я, однако, использую веб-элемент управления таблицей для таблиц с динамическими размерами (тех, чьи строки или столбцы зависят от определенных критериев базы данных или указаний пользователя), так как веб-элемент управления "Таблица" можно создавать программным образом.

Введите следующую разметку в <asp:Panel> тегах InsertingInterface панели:

<table class="DataWebControlStyle" cellspacing="0">
    <tr class="BatchInsertHeaderRow">
        <td class="BatchInsertLabel">Supplier:</td>
        <td></td>
        <td class="BatchInsertLabel">Category:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertAlternatingRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertAlternatingRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertFooterRow">
        <td colspan="4">
        </td>
    </tr>
</table>

Эта <table> разметка пока не включает никаких веб-управлений, мы добавим их в ближайшее время. Обратите внимание, что каждый <tr> элемент содержит определенный параметр класса CSS: BatchInsertHeaderRow для строки заголовка, в которой будут размещены DropDownLists для выбора поставщика и категории; BatchInsertFooterRow для строки нижнего колонтитула, где будут размещены кнопки "Добавить продукты из отправления" и "Отмена"; и чередующиеся значения BatchInsertRow и BatchInsertAlternatingRow для строк, в которых будут размещены элементы управления TextBox для продукта и цены за единицу. Я создал соответствующие классы CSS в Styles.css файле, чтобы предоставить интерфейс вставки внешний вид, аналогичный элементам управления GridView и DetailsView, которые мы использовали во всех этих руководствах. Эти классы CSS показаны ниже.

/*** Styles for ~/BatchData/BatchInsert.aspx tutorial ***/
.BatchInsertLabel
{
    font-weight: bold;
    text-align: right;
}
.BatchInsertHeaderRow td
{
    color: White;
    background-color: #900;
    padding: 11px;
}
.BatchInsertFooterRow td
{
    text-align: center;
    padding-top: 5px;
}
.BatchInsertRow
{
}
.BatchInsertAlternatingRow
{
    background-color: #fcc;
}

После ввода этой разметки вернитесь в режим конструктора. Это <table> должно отображаться как таблица с четырьмя столбцами и семью строками в конструкторе, как показано на рисунке 6.

Интерфейс вставки состоит из таблицы Seven-Row с четырьмя столбцами

Рис. 6. Интерфейс вставки состоит из четырех столбцов, Seven-Row таблицы (щелкните, чтобы просмотреть изображение полного размера)

Теперь мы готовы добавить веб-элементы управления в интерфейс вставки. Перетащите два раскрывающихся списка из панели элементов в соответствующие ячейки в таблице один для поставщика и один для категории.

Задайте свойству ID DropDownList поставщика Suppliers и привяжите его к новому объекту ObjectDataSource с именем SuppliersDataSource. Настройте новый ObjectDataSource, чтобы получать данные из метода класса SuppliersBLLGetSuppliers, и установите в раскрывающемся меню вкладки обновления значение (None). Нажмите кнопку "Готово", чтобы завершить работу мастера.

Настройка ObjectDataSource для использования метода GetSuppliers класса SuppliersBLL

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

Пусть в раскрывающемся списке Suppliers отображается поле данных CompanyName, а поле данных SupplierID используется в качестве значений ListItem.

Отображение поля данных

Рис. 8. Отображение CompanyName поля данных и использование SupplierID в качестве значения (щелкните, чтобы просмотреть изображение полного размера)

Назовите второй dropDownList Categories и привязите его к новому объекту ObjectDataSource с именем CategoriesDataSource. Настройте CategoriesDataSource ObjectDataSource для использования метода класса CategoriesBLLGetCategories. На вкладках UPDATE и DELETE в раскрывающемся списке выберите (Нет) и нажмите "Готово", чтобы завершить работу мастера. Наконец, настройте раскрывающийся список так, чтобы он отображал CategoryName поле данных и использовал CategoryID в качестве значения.

После добавления этих двух раскрывающихся списков и привязки к соответствующим настроенным ObjectDataSources экран должен выглядеть примерно так же, как на рис. 9.

Строка заголовка теперь содержит выпадающие списки поставщиков и категорий

Рис. 9: Строка заголовка теперь содержит Suppliers и Categories раскрывающиеся списки (нажмите, чтобы просмотреть полноразмерное изображение)

Теперь необходимо создать текстовые поля для сбора имени и цены для каждого нового продукта. Перетащите элемент управления TextBox из панели инструментов в конструктор для каждой из пяти строк с названием продукта и его ценой. Задайте значение свойств ID TextBoxes: ProductName1, UnitPrice1, ProductName2, UnitPrice2, ProductName3, UnitPrice3 и т. д.

Добавьте CompareValidator после каждого текстового поля единичной цены, установив ControlToValidate свойство соответствующим ID образом. Также установите для свойства Operator значение GreaterThanEqual, ValueToCompare на 0, и Type на Currency. Эти параметры предписывают CompareValidator убедиться, что цена, если она введена, является допустимым значением валюты, превышающим или равным нулю. Text Задайте для свойства значение *, а ErrorMessage цена должна быть больше или равно нулю. Кроме того, опустите любые символы валюты.

Замечание

Интерфейс вставки не содержит элементов управления RequiredFieldValidator, хотя поле ProductName в таблице базы данных Products не допускает значения NULL. Это связано с тем, что мы хотим разрешить пользователю вводить до пяти продуктов. Например, если пользователь предоставил имя продукта и цену единицы для первых трех строк, оставив последние две строки пустыми, мы просто добавим три новых продукта в систему. Поскольку ProductName необходимо, следует программно проверить, что, если введена цена за единицу, указано соответствующее значение названия продукта. Мы рассмотрим эту проверку на шаге 4.

При проверке входных данных пользователя функция CompareValidator сообщает недопустимые данные, если значение содержит символ валюты. Добавьте $ перед каждой ценой за единицу текстовых полей, чтобы служить визуальной подсказкой, которая указывает пользователю опустить символ валюты при вводе цены.

Наконец, добавьте элемент управления ValidationSummary в InsertingInterface панель, задав его ShowMessageBox свойство как true и его ShowSummary свойство как false. При использовании этих параметров, если пользователь вводит недопустимое значение цены на единицу, звездочка появится рядом с элементами управления TextBox, а в поле ValidationSummary появится клиентское поле сообщений, в котором отображается сообщение об ошибке, указанное ранее.

На этом этапе экран должен выглядеть примерно так, как на рис. 10.

Теперь интерфейс вставки включает текстовые поля для имен продуктов и цен

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

Затем нужно добавить в строку нижнего колонтитула кнопки "Добавить продукты из отправки" и "Отменить". Перетащите два элемента управления типа Button из панели элементов в нижний колонтитул интерфейса вставки, установив свойства кнопок ID и AddProducts, а также CancelButton и Text на "Добавить продукты из груза" и "Отменить" соответственно. Кроме того, установите для свойства CancelButton элемента управления CausesValidation значение false.

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

Перетащите элемент управления Label из панели элементов в начало страницы в конструкторе. Задайте для свойства ID значение StatusLabel, очистите свойство Text, и установите значения Visible и EnableViewState в false. Как мы видели в предыдущих руководствах, установив EnableViewState свойство на false, мы можем программно изменять значения свойств Label и автоматически возвращать их к значениям по умолчанию при последующей обратной передаче. Это упрощает отображение сообщения о состоянии в ответ на какое-либо действие пользователя, которое исчезает при последующей обратной отправке. Наконец, задайте свойству StatusLabel элемента управления CssClass значение Warning, что является именем класса CSS, определенного в Styles.css, который отображает текст крупным курсивным полужирным красным шрифтом.

На рисунке 11 показан конструктор Visual Studio после добавления и настройки метки.

Поместите элемент управления StatusLabel над двумя элементами управления панели

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

Шаг 3. Переключение между интерфейсами отображения и вставки

На этом этапе мы завершили разметку для отображения и вставки интерфейсов, но мы все еще остаемся с двумя задачами:

  • Переключение между интерфейсами отображения и вставки
  • Добавление продуктов из отгрузки в базу данных

В настоящее время интерфейс отображения отображается, но интерфейс вставки скрыт. Это связано с тем, что DisplayInterface для свойства Панели Visible задано true значение (значение по умолчанию), а InsertingInterface для свойства Панели Visible задано значение false. Чтобы переключиться между двумя интерфейсами, необходимо просто переключить значение свойства каждого элемента управления Visible .

Мы хотим перейти от интерфейса отображения к интерфейсу ввода, когда нажимается кнопка "Процесс доставки продукта". Поэтому создайте обработчик событий для этого события Button Click , содержащего следующий код:

protected void ProcessShipment_Click(object sender, EventArgs e)
{
    DisplayInterface.Visible = false;
    InsertingInterface.Visible = true;
}

Этот код просто скрывает DisplayInterface панель и отображает InsertingInterface панель.

Затем создайте обработчики событий для элементов управления "Добавить продукты из отправки" и "кнопка отмены" в интерфейсе вставки. При щелчке любого из этих кнопок необходимо вернуться к интерфейсу отображения. Создайте Click обработчики событий для обеих кнопок, чтобы они вызывали ReturnToDisplayInterface, метод, который мы скоро добавим. Помимо скрытия InsertingInterface Панели и отображения DisplayInterface Панели, метод ReturnToDisplayInterface должен вернуть веб-элементы управления в состояние до редактирования. Это включает установку свойств DropDownLists SelectedIndex в значение 0 и очистку свойств элементов управления TextBox Text.

Замечание

Рассмотрим, что может произойти, если мы не возвращали элементы управления в состояние предварительного редактирования, прежде чем вернуться в интерфейс отображения. Пользователь может нажать кнопку "Обработка отправки продукта", ввести продукты из отгрузки, а затем нажать кнопку "Добавить продукты из отгрузки". Это приведет к добавлению продуктов и возврату пользователя в интерфейс отображения. На этом этапе пользователю может потребоваться добавить другой груз. После нажатия кнопки "Обработка отправки продукта" они вернутся в интерфейс вставки, но значения выпадающего списка и текстовых полей по-прежнему будут заполнены их предыдущими значениями.

protected void AddProducts_Click(object sender, EventArgs e)
{
    // TODO: Save the products
    // Revert to the display interface
    ReturnToDisplayInterface();
}
protected void CancelButton_Click(object sender, EventArgs e)
{
    // Revert to the display interface
    ReturnToDisplayInterface();
}
const int firstControlID = 1;
const int lastControlID = 5;
private void ReturnToDisplayInterface()
{
    // Reset the control values in the inserting interface
    Suppliers.SelectedIndex = 0;
    Categories.SelectedIndex = 0;
    for (int i = firstControlID; i <= lastControlID; i++)
    {
        ((TextBox)InsertingInterface.FindControl("ProductName" + i.ToString())).Text =
            string.Empty;
        ((TextBox)InsertingInterface.FindControl("UnitPrice" + i.ToString())).Text = 
            string.Empty;
    }
    DisplayInterface.Visible = true;
    InsertingInterface.Visible = false;
}

Оба Click обработчика событий просто вызывают ReturnToDisplayInterface метод, хотя мы вернемся к обработчику событий Add Products from Shipment Click на шаге 4 и добавим код для сохранения продуктов. ReturnToDisplayInterface начинает с возврата выпадающих списков SuppliersCategories на первые опции. Две константы firstControlID и lastControlID помечают начальные и конечные значения индекса элемента управления, используемые при именовании имени продукта и цен единиц в интерфейсе вставки и используются в границах for цикла, который задает Text свойства элементов управления TextBox обратно в пустую строку. Наконец, свойства панелей Visible сбрасываются, чтобы скрыть интерфейс вставки и отобразить интерфейс отображения.

Проверьте эту страницу в браузере. При первом посещении страницы вы увидите интерфейс отображения, как показано на рис. 5. Нажмите кнопку "Процесс отправки продукта". Страница обновится, и теперь вы увидите интерфейс вставки, как показано на рис. 12. Нажатие кнопок "Добавить продукты из отправки" или "Отмена" вернёт вас к интерфейсу отображения.

Замечание

При просмотре интерфейса вставки уделите немного времени, чтобы проверить CompareValidators на текстовых полях с ценой за единицу. При нажатии кнопки "Добавить продукты из отгрузки" появится предупреждающее сообщение на стороне клиента с недопустимыми значениями валюты или ценами со значением меньше нуля.

Интерфейс добавления отображается после нажатия кнопки

Рис. 12. Интерфейс вставки отображается после нажатия кнопки "Отправка продукта процесса" (щелкните, чтобы просмотреть изображение полного размера)

Шаг 4. Добавление продуктов

Все, что осталось для этого руководства, — это сохранить продукты в базе данных в обработчике событий кнопки "Добавить продукты из отгрузки" Click. Это можно сделать, создав ProductsDataTable и добавив экземпляр ProductsRow для каждого из предоставленных имен продуктов. После того как были добавлены эти ProductsRow, мы вызовем метод ProductsBLL класса UpdateWithTransaction, передав ему ProductsDataTable. Помните, что метод UpdateWithTransaction, который был создан в руководстве «Обертывание изменений в базе данных внутри транзакции», передает ProductsDataTable методу ProductsTableAdapterUpdateWithTransaction. После этого запускается транзакция ADO.NET, и TableAdapter отправляет INSERT команду в базу данных для каждой добавленной ProductsRow в DataTable. Предполагая, что все продукты добавляются без ошибок, транзакция фиксируется, в противном случае она откатывается.

Код обработчика событий для кнопки "Добавить продукты из отгрузки" Click также должен выполнять некоторую проверку ошибок. Из-за отсутствия обязательных валидаторов полей в интерфейсе добавления, пользователь может ввести цену для продукта, не указывая его название. Так как требуется имя продукта, если возникает такое условие, необходимо незамедлительно предупредить пользователя и не выполнять вставки. Следующий код полного Click обработчика событий:

protected void AddProducts_Click(object sender, EventArgs e)
{
    // Make sure that the UnitPrice CompareValidators report valid data...
    if (!Page.IsValid)
        return;
    // Add new ProductsRows to a ProductsDataTable...
    Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
    for (int i = firstControlID; i <= lastControlID; i++)
    {
        // Read in the values for the product name and unit price
        string productName = ((TextBox)InsertingInterface.FindControl
            ("ProductName" + i.ToString())).Text.Trim();
        string unitPrice = ((TextBox)InsertingInterface.FindControl
            ("UnitPrice" + i.ToString())).Text.Trim();
        // Ensure that if unitPrice has a value, so does productName
        if (unitPrice.Length > 0 && productName.Length == 0)
        {
            // Display a warning and exit this event handler
            StatusLabel.Text = "If you provide a unit price you must also " +
                "include the name of the product.";
            StatusLabel.Visible = true;
            return;
        }
        // Only add the product if a product name value is provided
        if (productName.Length > 0)
        {
            // Add a new ProductsRow to the ProductsDataTable
            Northwind.ProductsRow newProduct = products.NewProductsRow();
            // Assign the values from the web page
            newProduct.ProductName = productName;
            newProduct.SupplierID = Convert.ToInt32(Suppliers.SelectedValue);
            newProduct.CategoryID = Convert.ToInt32(Categories.SelectedValue);
            if (unitPrice.Length > 0)
                newProduct.UnitPrice = Convert.ToDecimal(unitPrice);
            // Add any "default" values
            newProduct.Discontinued = false;
            newProduct.UnitsOnOrder = 0;
            products.AddProductsRow(newProduct);
        }
    }
    // If we reach here, see if there were any products added
    if (products.Count > 0)
    {
        // Add the new products to the database using a transaction
        ProductsBLL productsAPI = new ProductsBLL();
        productsAPI.UpdateWithTransaction(products);
        // Rebind the data to the grid so that the products just added are displayed
        ProductsGrid.DataBind();
        // Display a confirmation (don't use the Warning CSS class, though)
        StatusLabel.CssClass = string.Empty;
        StatusLabel.Text = string.Format(
            "{0} products from supplier {1} have been added and filed under " + 
            "category {2}.", products.Count, Suppliers.SelectedItem.Text, 
            Categories.SelectedItem.Text);
        StatusLabel.Visible = true;
        // Revert to the display interface
        ReturnToDisplayInterface();
    }
    else
    {
        // No products supplied!
        StatusLabel.Text = "No products were added. Please enter the product " + 
            "names and unit prices in the textboxes.";
        StatusLabel.Visible = true;
    }
}

Обработчик событий начинается с обеспечения того, что Page.IsValid свойство возвращает значение true. Если возвращается false, это означает, что один или несколько CompareValidator сообщают о недопустимых данных. В таком случае мы не должны пытаться вставлять введенные продукты, иначе при попытке назначить пользователем введенное значение стоимости за единицу в свойство ProductsRowUnitPrice возникнет исключение.

Затем создается новый ProductsDataTable экземпляр (products). Цикл for используется для перебора TextBox'ов с именем продукта и ценой за единицу, а свойства Text считываются в локальные переменные productName и unitPrice. Если пользователь ввёл значение для цены за единицу продукции, но не указал соответствующее имя продукта, отображается сообщение: StatusLabel Если вы указываете цену за единицу, необходимо также включить имя продукта, и обработчик событий завершает выполнение.

Если указано имя продукта, создается новый ProductsRow экземпляр с помощью метода ProductsDataTablesNewProductsRow. Свойство нового экземпляра ProductsRowProductName установлено на текущее имя продукта в TextBox, а свойства SupplierID и CategoryID назначаются свойствам SelectedValue в выпадающих списках в заголовке интерфейса вставки. Если пользователь ввёл значение для цены продукта, оно назначается свойству ProductsRow экземпляра UnitPrice; в противном случае свойство остаётся неназначенным, что приведет к значению NULL для UnitPrice в базе данных. Наконец Discontinued и UnitsOnOrder свойствам присваиваются значения false и 0 соответственно.

После того как экземпляру ProductsRow назначили свойства, его добавляют в ProductsDataTable.

По завершении for цикла мы проверяем, были ли добавлены продукты. Пользователь, в конечном счёте, мог нажать кнопку "Добавить продукты из отгрузки" перед вводом любых названий продуктов или цен. Если в ProductsDataTable есть хотя бы один продукт, вызывается метод ProductsBLL класса UpdateWithTransaction. Затем данные привязываются заново к ProductsGrid GridView, чтобы вновь добавленные товары отображались в интерфейсе. Обновляется StatusLabel для отображения сообщения подтверждения, и ReturnToDisplayInterface вызывается, скрывается интерфейс вставки и показывается интерфейс отображения.

Если продукты не были введены, интерфейс вставки остается на экране, но появляется сообщение "Продукты не были добавлены." Введите названия продуктов и цены за единицы в соответствующих текстовых полях.

На рисунках 13, 14 и 15 показаны интерфейсы вставки и отображения в работе. На рис. 13 пользователь ввел цену единицы без соответствующего имени продукта. На рисунке 14 показан интерфейс отображения после успешного добавления трех новых продуктов, а на рисунке 15 показаны два недавно добавленных продукта в GridView (третий — на предыдущей странице).

Имя продукта необходимо при вводе цены на единицу

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

Три новых овощей были добавлены поставщику Маюми

Рис. 14. Добавлены три новых veggies для поставщика Майуми (щелкните, чтобы просмотреть изображение полного размера)

Новые продукты можно найти на последней странице GridView

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

Замечание

Логика пакетной вставки, используемая в этом руководстве, оборачивает вставки в контекст транзакции. Чтобы проверить это, намеренно введите ошибку уровня базы данных. Например, вместо назначения свойства нового ProductsRow экземпляра CategoryID выбранному значению в Categories DropDownList присвойте ему значение, такое как i * 5. Вот i — индексатор цикла со значениями от 1 до 5. Поэтому при добавлении двух или более продуктов в пакетную вставку первого продукта будет иметь допустимое CategoryID значение (5), но последующие продукты будут иметь CategoryID значения, которые не соответствуют CategoryID значениям в Categories таблице. Чистый эффект заключается в том, что хотя первый INSERT завершится успешно, следующие из них завершатся ошибкой с нарушением ограничения внешнего ключа. Поскольку пакетная вставка является атомарной операцией, первая INSERT будет отменена, возвращая базу данных в состояние, в котором она находилась до начала процесса пакетной вставки.

Сводка

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

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

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

Сведения о авторе

Скотт Митчелл, автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с технологиями Microsoft Web с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга — Sams Teach Yourself ASP.NET 2.0 за 24 часа. С ним можно связаться по адресу mitchell@4GuysFromRolla.com.

Особое спасибо кому

Эта серия учебников была проверена многими полезными рецензентами. Ведущие рецензенты для этого руководства были Хилтон Гисеноу и Сорен Якоб Лоритсен. Хотите просмотреть мои предстоящие статьи MSDN? Если да, напишите мне на mitchell@4GuysFromRolla.com.