Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Узнайте, как вставить несколько записей базы данных в одну операцию. В уровне пользовательского интерфейса мы расширим 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 в положение (Нет). Нажмите кнопку "Готово", чтобы завершить настройку источника данных.
Рис. 3. Отображение данных, возвращаемых из ProductsBLL
метода класса GetProducts
(щелкните, чтобы просмотреть изображение полного размера)
Рис. 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, которая перечисляет продукты по десять за раз.
Рис. 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.
Рис. 6. Интерфейс вставки состоит из четырех столбцов, Seven-Row таблицы (щелкните, чтобы просмотреть изображение полного размера)
Теперь мы готовы добавить веб-элементы управления в интерфейс вставки. Перетащите два раскрывающихся списка из панели элементов в соответствующие ячейки в таблице один для поставщика и один для категории.
Задайте свойству ID
DropDownList поставщика Suppliers
и привяжите его к новому объекту ObjectDataSource с именем SuppliersDataSource
. Настройте новый ObjectDataSource, чтобы получать данные из метода класса SuppliersBLL
GetSuppliers
, и установите в раскрывающемся меню вкладки обновления значение (None). Нажмите кнопку "Готово", чтобы завершить работу мастера.
Рис. 7. Настройка ObjectDataSource для использования SuppliersBLL
метода класса GetSuppliers
(щелкните, чтобы просмотреть изображение полного размера)
Пусть в раскрывающемся списке Suppliers
отображается поле данных CompanyName
, а поле данных SupplierID
используется в качестве значений ListItem
.
Рис. 8. Отображение CompanyName
поля данных и использование SupplierID
в качестве значения (щелкните, чтобы просмотреть изображение полного размера)
Назовите второй dropDownList Categories
и привязите его к новому объекту ObjectDataSource с именем CategoriesDataSource
. Настройте CategoriesDataSource
ObjectDataSource для использования метода класса CategoriesBLL
GetCategories
. На вкладках 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 после добавления и настройки метки.
Рис. 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
начинает с возврата выпадающих списков Suppliers
Categories
на первые опции. Две константы firstControlID
и lastControlID
помечают начальные и конечные значения индекса элемента управления, используемые при именовании имени продукта и цен единиц в интерфейсе вставки и используются в границах for
цикла, который задает Text
свойства элементов управления TextBox обратно в пустую строку. Наконец, свойства панелей Visible
сбрасываются, чтобы скрыть интерфейс вставки и отобразить интерфейс отображения.
Проверьте эту страницу в браузере. При первом посещении страницы вы увидите интерфейс отображения, как показано на рис. 5. Нажмите кнопку "Процесс отправки продукта". Страница обновится, и теперь вы увидите интерфейс вставки, как показано на рис. 12. Нажатие кнопок "Добавить продукты из отправки" или "Отмена" вернёт вас к интерфейсу отображения.
Замечание
При просмотре интерфейса вставки уделите немного времени, чтобы проверить CompareValidators на текстовых полях с ценой за единицу. При нажатии кнопки "Добавить продукты из отгрузки" появится предупреждающее сообщение на стороне клиента с недопустимыми значениями валюты или ценами со значением меньше нуля.
Рис. 12. Интерфейс вставки отображается после нажатия кнопки "Отправка продукта процесса" (щелкните, чтобы просмотреть изображение полного размера)
Шаг 4. Добавление продуктов
Все, что осталось для этого руководства, — это сохранить продукты в базе данных в обработчике событий кнопки "Добавить продукты из отгрузки" Click
. Это можно сделать, создав ProductsDataTable
и добавив экземпляр ProductsRow
для каждого из предоставленных имен продуктов. После того как были добавлены эти ProductsRow
, мы вызовем метод ProductsBLL
класса UpdateWithTransaction
, передав ему ProductsDataTable
. Помните, что метод UpdateWithTransaction
, который был создан в руководстве «Обертывание изменений в базе данных внутри транзакции», передает ProductsDataTable
методу ProductsTableAdapter
UpdateWithTransaction
. После этого запускается транзакция 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 сообщают о недопустимых данных. В таком случае мы не должны пытаться вставлять введенные продукты, иначе при попытке назначить пользователем введенное значение стоимости за единицу в свойство ProductsRow
UnitPrice
возникнет исключение.
Затем создается новый ProductsDataTable
экземпляр (products
). Цикл for
используется для перебора TextBox'ов с именем продукта и ценой за единицу, а свойства Text
считываются в локальные переменные productName
и unitPrice
. Если пользователь ввёл значение для цены за единицу продукции, но не указал соответствующее имя продукта, отображается сообщение: StatusLabel
Если вы указываете цену за единицу, необходимо также включить имя продукта, и обработчик событий завершает выполнение.
Если указано имя продукта, создается новый ProductsRow
экземпляр с помощью метода ProductsDataTable
sNewProductsRow
. Свойство нового экземпляра ProductsRow
ProductName
установлено на текущее имя продукта в 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 для поставщика Майуми (щелкните, чтобы просмотреть изображение полного размера)
Рис. 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.