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

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

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

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

Введение

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

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

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

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

Рис. 1. Интерфейс пакетной вставки (щелкните для просмотра полноразмерного изображения)

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

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

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

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

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

Перетащите панель с панели элементов на Designer

Рис. 2. Перетащите панель с панели элементов на Designer (щелкните для просмотра полноразмерного изображения)

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

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

Рис. 3. Отображение данных, возвращаемых методом ProductsBLL Class s 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 Панели, их можно скрыть, просто задав для свойства Panel значение Visiblefalse. На шаге 3 рассматривается программное изменение свойства Panel Visible в ответ на нажатие кнопки для отображения одного интерфейса при скрытии другого.

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

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

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

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

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

Сначала перетащите панель из панели элементов на Designer, поместив ее под существующую DisplayInterface панель. Присвойте свойству ID этой добавленной панели значение InsertingInterface , а его Visible свойству — значение false. Мы добавим код, который задает InsertingInterface для свойства Panel значение trueVisible на шаге 3. Также очистите значения свойств Panel и HeightWidth .

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

Примечание

При вводе разметки для элементов HTML <table> я предпочитаю использовать представление Source. Хотя в Visual Studio есть средства для добавления <table> элементов через Designer, Designer, кажется, слишком готовы внедрять в разметку не заданные style параметры. После создания разметки <table> я обычно возвращаюсь к Designer, чтобы добавить веб-элементы управления и задать их свойства. При создании таблиц с предварительно определенными столбцами и строками я предпочитаю использовать статический 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> должен отображаться в виде таблицы из четырех столбцов, семи строк в Designer, как показано на рисунке 6.

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

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

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

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

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

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

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

Отображение поля данных CompanyName и использование Идентификатор поставщика в качестве значения

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

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

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

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

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

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

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

Примечание

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Затем создайте обработчики событий для элементов управления Add Products from Shipment (Добавить продукты из отправки) и Cancel Button (Кнопка отмены) в интерфейсе вставки. При нажатии любой из этих кнопок необходимо отменить изменения обратно к интерфейсу отображения. Создайте Click обработчики событий для обоих элементов управления Button, чтобы они вызывали ReturnToDisplayInterfaceметод , который мы добавим мгновенно. Помимо скрытия InsertingInterface панели и отображения DisplayInterface панели метод ReturnToDisplayInterface должен вернуть веб-элементы управления в состояние предварительного редактирования. Для этого необходимо задать для свойств DropDownLists SelectedIndex значение 0 и очистить Text свойства элементов управления TextBox.

Примечание

Подумайте, что может произойти, если мы не вернем элементы управления в состояние предварительного редактирования, прежде чем вернуться в интерфейс отображения. Пользователь может нажать кнопку Process Product Shipment (Обработка отгрузки продукта), ввести продукты из отгрузки, а затем нажать кнопку Добавить продукты из отправки. Это приведет к добавлению продуктов и возврату пользователя в интерфейс отображения. На этом этапе пользователю может потребоваться добавить еще одну отправку. После нажатия кнопки Процесс отправки продукта они будут возвращаться в интерфейс вставки, но значения выбора DropDownList и TextBox по-прежнему будут заполнены их предыдущими значениями.

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 Начинается с возврата раскрывающихся Suppliers списков и Categories к первым параметрам. Две константы firstControlID и lastControlID помечают начальные и конечные значения индекса элементов управления, используемые при именовании названия продукта и цены за единицу TextBox в интерфейсе вставки и используются в границах for цикла, который задает Text свойства элементов управления TextBox в пустую строку. Наконец, свойства Panels Visible сбрасываются, чтобы интерфейс вставки был скрыт, а интерфейс отображения отображается.

Проверьте эту страницу в браузере. При первом посещении страницы вы увидите интерфейс отображения, как показано на рисунке 5. Нажмите кнопку Обработать отправку продукта. Страница будет обратной передачи, и теперь вы увидите интерфейс вставки, как показано на рисунке 12. При нажатии кнопки Add Products from Shipment (Добавить продукты из отгрузки) или Cancel (Отмена) вы перейдете к интерфейсу отображения.

Примечание

При просмотре интерфейса вставки проверьте параметры CompareValidators на полях TextBoxes цены за единицу. При нажатии кнопки Add Products from Shipment (Добавить продукты из отгрузки) при нажатии кнопки Add Products from Shipment (Добавить продукты из отгрузки) должно появиться предупреждение о недопустом значении валюты или ценах со значением меньше нуля.

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

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

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

Все, что осталось для этого руководства, — сохранить продукты в базе данных в обработчике Click событий Add Products from Shipment Button. Это можно сделать, создав ProductsDataTable и добавив ProductsRow экземпляр для каждого из предоставленных имен продуктов. После добавления этих ProductsRow команд мы будем выполнять вызов ProductsBLL метода класса , UpdateWithTransaction передавая ProductsDataTableв . Помните, что UpdateWithTransaction метод, который был создан еще в руководстве по переносу изменений базы данных в рамках транзакции , передает ProductsDataTableProductsTableAdapter в метод s UpdateWithTransaction . После этого запускается транзакция ADO.NET, и TableAdapter выдает инструкцию INSERT в базу данных для каждого добавленного ProductsRow в таблицу DataTable. При условии, что все продукты добавляются без ошибок, транзакция фиксируется, в противном случае выполняется откат.

Код для обработчика Click событий Add Products from Shipment Button также должен выполнять проверку ошибок. Так как в интерфейсе вставки не используется requiredFieldValidators, пользователь может ввести цену на продукт, опустив его имя. Так как название продукта является обязательным, в случае развертывания такого условия необходимо предупредить пользователя и не продолжать вставку. Полный 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значение , это означает, что один или несколько объектов CompareValidators сообщают недопустимые данные. В этом случае мы не хотим пытаться вставить введенные продукты, иначе при попытке назначить введенное пользователем значение цены за единицу свойству ProductsRow s UnitPrice в конечном итоге возникнет исключение.

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

Если указано название продукта, создается новый ProductsRow экземпляр с помощью ProductsDataTable метода s NewProductsRow . Этому новому ProductsRow свойству экземпляра ProductName присваивается текущее имя продукта TextBox, а SupplierID свойства и CategoryIDSelectedValue свойствам DropDownLists в заголовке интерфейса вставки. Если пользователь ввел значение для цены продукта, оно назначается свойству 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. При вводе цены за единицу требуется название продукта (щелкните для просмотра полноразмерного изображения)

Три новых veggies были добавлены для поставщика Mayumi

Рис. 14. Три новых veggies были добавлены для поставщика Mayumi (щелкните для просмотра полноразмерного изображения)

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

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

Примечание

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

Сводка

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

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

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

Об авторе

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