Inserção em lote (C#)
por Scott Mitchell
Saiba como inserir vários registros de banco de dados em uma única operação. Na Camada de Interface do Usuário, estendemos o GridView para permitir que o usuário insira vários novos registros. Na Camada de Acesso a Dados, encapsulamos as várias operações inserir dentro de uma transação para garantir que todas as inserções sejam bem-sucedidas ou todas as inserções sejam revertidas.
Introdução
No tutorial Atualização em Lote , examinamos a personalização do controle GridView para apresentar uma interface em que vários registros eram editáveis. O usuário que visita a página pode fazer uma série de alterações e, com um único clique de botão, executar uma atualização em lote. Para situações em que os usuários normalmente atualizam muitos registros de uma só vez, essa interface pode salvar inúmeros cliques e opções de contexto de teclado para mouse em comparação com os recursos padrão de edição por linha que foram explorados pela primeira vez no tutorial Uma Visão geral de inserção, atualização e exclusão de dados .
Esse conceito também pode ser aplicado ao adicionar registros. Imagine que aqui na Northwind Traders normalmente recebemos remessas de fornecedores que contêm vários produtos para uma determinada categoria. Por exemplo, podemos receber uma remessa de seis produtos diferentes de chá e café da Tokyo Traders. Se um usuário inserir os seis produtos um de cada vez por meio de um controle DetailsView, ele terá que escolher muitos dos mesmos valores repetidamente: ele precisará escolher a mesma categoria (Bebidas), o mesmo fornecedor (Tokyo Traders), o mesmo valor descontinuado (False) e as mesmas unidades no valor do pedido (0). Essa entrada de dados repetitiva não é apenas demorada, mas é propensa a erros.
Com um pouco de trabalho, podemos criar uma interface de inserção em lote que permite que o usuário escolha o fornecedor e a categoria uma vez, insira uma série de nomes de produtos e preços unitários e clique em um botão para adicionar os novos produtos ao banco de dados (consulte a Figura 1). À medida que cada produto é adicionado, seus ProductName
campos de dados e UnitPrice
são atribuídos aos valores inseridos em TextBoxes, enquanto seus CategoryID
valores e SupplierID
são atribuídos aos valores de DropDownLists na parte superior do formulário. Os Discontinued
valores e UnitsOnOrder
são definidos como os valores embutidos em código de false
e 0, respectivamente.
Figura 1: a interface de inserção em lote (clique para exibir a imagem em tamanho real)
Neste tutorial, criaremos uma página que implementa a interface de inserção em lote mostrada na Figura 1. Assim como nos dois tutoriais anteriores, encapsularemos as inserções dentro do escopo de uma transação para garantir a atomicidade. Vamos começar!
Etapa 1: Criando a interface de exibição
Este tutorial consistirá em uma única página dividida em duas regiões: uma região de exibição e uma região de inserção. A interface de exibição, que criaremos nesta etapa, mostra os produtos em um GridView e inclui um botão intitulado Processar Remessa de Produtos. Quando esse botão é clicado, a interface de exibição é substituída pela interface de inserção, que é mostrada na Figura 1. A interface de exibição retorna depois que os botões Adicionar Produtos de Remessa ou Cancelar são clicados. Criaremos a interface de inserção na Etapa 2.
Ao criar uma página que tenha duas interfaces, apenas uma das quais é visível por vez, cada interface normalmente é colocada dentro de um controle Da Web do Painel, que serve como um contêiner para outros controles. Portanto, nossa página terá dois controles panel um para cada interface.
Comece abrindo a BatchInsert.aspx
página na BatchData
pasta e arraste um Painel da Caixa de Ferramentas para o Designer (consulte a Figura 2). Defina a propriedade Do ID
painel como DisplayInterface
. Ao adicionar o Painel ao Designer, suas Height
propriedades e Width
são definidas como 50px e 125px, respectivamente. Limpe esses valores de propriedade do janela Propriedades.
Figura 2: Arraste um Painel da Caixa de Ferramentas para o Designer (Clique para exibir a imagem em tamanho real)
Em seguida, arraste um controle Button e GridView para o Painel. Defina a propriedade Button s ID
como ProcessShipment
e sua Text
propriedade como Processar Remessa de Produto. Defina a propriedade gridView como ID
e, de sua marca inteligente, associe-a a um novo ObjectDataSource chamado ProductsDataSource
.ProductsGrid
Configure o ObjectDataSource para efetuar pull de seus dados do ProductsBLL
método da classe s GetProducts
. Como esse GridView é usado apenas para exibir dados, defina as listas suspensas nas guias UPDATE, INSERT e DELETE como (Nenhum). Clique em Concluir para concluir o assistente Configurar Fonte de Dados.
Figura 3: Exibir os dados retornados do ProductsBLL
método da GetProducts
classe (clique para exibir a imagem em tamanho real)
Figura 4: Definir o Drop-Down Listas nas guias UPDATE, INSERT e DELETE como (Nenhum) (Clique para exibir a imagem em tamanho real)
Depois de concluir o assistente ObjectDataSource, o Visual Studio adicionará BoundFields e um CheckBoxField para os campos de dados do produto. Remova todos, exceto os ProductName
campos , CategoryName
, SupplierName
UnitPrice
, e Discontinued
. Sinta-se à vontade para fazer personalizações estéticas. Decidi formatar o UnitPrice
campo como um valor de moeda, reordenei os campos e renomeei vários dos valores de campos HeaderText
. Configure também o GridView para incluir paginação e suporte à classificação marcando as caixas de seleção Habilitar Paginação e Habilitar Classificação na marca inteligente GridView.
Depois de adicionar os controles Panel, Button, GridView e ObjectDataSource e personalizar os campos do GridView, a marcação declarativa da página deve ser semelhante à seguinte:
<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>
Observe que a marcação para Button e GridView aparece dentro das marcas de abertura e fechamento <asp:Panel>
. Como esses controles estão dentro do Painel, podemos ocultá-los DisplayInterface
simplesmente definindo a propriedade do Visible
Painel como false
. A Etapa 3 analisa a alteração programática da propriedade do Visible
Painel em resposta a um clique de botão para mostrar uma interface enquanto oculta a outra.
Reserve um momento para ver nosso progresso por meio de um navegador. Como mostra a Figura 5, você deve ver um botão Processar Remessa de Produto acima de um GridView que lista os produtos dez de cada vez.
Figura 5: o GridView Listas os recursos de classificação e paginação de produtos e ofertas (clique para exibir a imagem em tamanho real)
Etapa 2: Criando a interface de inserção
Com a interface de exibição concluída, estamos prontos para criar a interface de inserção. Para este tutorial, vamos criar uma interface de inserção que solicita um único valor de fornecedor e categoria e, em seguida, permite que o usuário insira até cinco nomes de produtos e valores de preço unitário. Com essa interface, o usuário pode adicionar um a cinco novos produtos que compartilham a mesma categoria e fornecedor, mas têm nomes e preços exclusivos de produtos.
Comece arrastando um Painel da Caixa de Ferramentas para o Designer, colocando-o abaixo do Painel existenteDisplayInterface
. Defina a ID
propriedade desse Painel recém-adicionado como InsertingInterface
e defina sua propriedade false
como Visible
. Adicionaremos código que define a InsertingInterface
propriedade do Visible
Painel como true
na Etapa 3. Desmarque também os valores da propriedade e do Height
Width
Painel.
Em seguida, precisamos criar a interface de inserção que foi mostrada de volta na Figura 1. Essa interface pode ser criada por meio de uma variedade de técnicas HTML, mas usaremos uma bastante simples: uma tabela de quatro colunas e sete linhas.
Observação
Ao inserir marcação para elementos HTML <table>
, prefiro usar o modo de exibição Origem. Embora o Visual Studio tenha ferramentas para adicionar <table>
elementos por meio do Designer, o Designer parece muito disposto a injetar sem assistência para style
configurações na marcação. Depois de criar a <table>
marcação, geralmente retorno ao Designer para adicionar os controles da Web e definir suas propriedades. Ao criar tabelas com colunas e linhas predeterminadas, prefiro usar HTML estático em vez do controle Da Web de Tabela porque todos os controles da Web colocados em um controle Da Web de Tabela só podem ser acessados usando o FindControl("controlID")
padrão . No entanto, uso controles da Web de Tabela para tabelas de tamanho dinâmico (aqueles cujas linhas ou colunas são baseadas em alguns critérios especificados pelo banco de dados ou pelo usuário), uma vez que o controle Da Web de Tabela pode ser construído programaticamente.
Insira a seguinte marcação dentro das <asp:Panel>
marcas do InsertingInterface
Painel:
<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>
Essa <table>
marcação ainda não inclui nenhum controle da Web, vamos adicioná-los momentaneamente. Observe que cada <tr>
elemento contém uma configuração de classe CSS específica: BatchInsertHeaderRow
para a linha de cabeçalho para a qual o fornecedor e a categoria DropDownLists irão; BatchInsertFooterRow
para a linha de rodapé onde os botões Adicionar Produtos de Remessa e Cancelar irão; e alternar BatchInsertRow
e BatchInsertAlternatingRow
valores para as linhas que conterão os controles TextBox de preço unitário e produto. Criei classes CSS correspondentes no Styles.css
arquivo para dar à interface de inserção uma aparência semelhante aos controles GridView e DetailsView que usamos ao longo desses tutoriais. Essas classes CSS são mostradas abaixo.
/*** 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;
}
Com essa marcação inserida, retorne ao modo design. Isso <table>
deve ser mostrado como uma tabela de quatro colunas e sete linhas no Designer, como ilustra a Figura 6.
Figura 6: a interface de inserção é composta por uma tabela de quatro colunas Seven-Row (clique para exibir a imagem em tamanho real)
Agora estamos prontos para adicionar os controles da Web à interface de inserção. Arraste duas DropDownLists da Caixa de Ferramentas para as células apropriadas na tabela um para o fornecedor e outra para a categoria.
Defina a propriedade Suppliers
do fornecedor DropDownList como ID
e associe-a a um novo ObjectDataSource chamado SuppliersDataSource
. Configure o novo ObjectDataSource para recuperar seus dados do SuppliersBLL
método da classe s GetSuppliers
e defina a lista suspensa da guia UPDATE como (Nenhum). Clique em Concluir para concluir o assistente.
Figura 7: Configurar o ObjectDataSource para usar o SuppliersBLL
método da classe (GetSuppliers
clique para exibir a imagem em tamanho real)
Fazer com que o Suppliers
DropDownList exiba o CompanyName
campo de dados e use o SupplierID
campo de dados como seus ListItem
valores s.
Figura 8: Exibir o CompanyName
Campo de Dados e Usar SupplierID
como o Valor (Clique para exibir a imagem em tamanho real)
Nomeie o segundo DropDownList Categories
e associe-o a um novo ObjectDataSource chamado CategoriesDataSource
. Configure o CategoriesDataSource
ObjectDataSource para usar o CategoriesBLL
método da classe s GetCategories
; defina as listas suspensas nas guias UPDATE e DELETE como (Nenhum) e clique em Concluir para concluir o assistente. Por fim, fazer com que o DropDownList exiba o CategoryName
campo de dados e use o CategoryID
como o valor.
Depois que esses dois DropDownLists tiverem sido adicionados e associados a ObjectDataSources configurados adequadamente, sua tela deverá ser semelhante à Figura 9.
Figura 9: a linha de cabeçalho agora contém o Suppliers
e Categories
DropDownLists (clique para exibir a imagem em tamanho real)
Agora precisamos criar as TextBoxes para coletar o nome e o preço de cada novo produto. Arraste um controle TextBox da Caixa de Ferramentas para a Designer para cada uma das cinco linhas de nome e preço do produto. Defina as ID
propriedades de TextBoxes como ProductName1
, UnitPrice1
, ProductName2
, UnitPrice2
, ProductName3
, UnitPrice3
e assim por diante.
Adicione um CompareValidator após cada um dos TextBoxes de preço unitário, definindo a ControlToValidate
propriedade como o apropriado ID
. Defina também a Operator
propriedade como GreaterThanEqual
, ValueToCompare
como 0 e Type
como Currency
. Essas configurações instruem o CompareValidator a garantir que o preço, se inserido, seja um valor de moeda válido maior ou igual a zero. Defina a Text
propriedade como *e ErrorMessage
como O preço deve ser maior ou igual a zero. Além disso, omita todos os símbolos de moeda.
Observação
A interface de inserção não inclui nenhum controle RequiredFieldValidator, embora o ProductName
campo na tabela de Products
banco de dados não permita NULL
valores. Isso ocorre porque queremos permitir que o usuário insira até cinco produtos. Por exemplo, se o usuário fornecesse o nome do produto e o preço unitário das três primeiras linhas, deixando as duas últimas linhas em branco, apenas adicionaríamos três novos produtos ao sistema. Como ProductName
é necessário, no entanto, precisaremos marcar programaticamente para garantir que, se um preço unitário for inserido, um valor de nome de produto correspondente seja fornecido. Abordaremos esse marcar na Etapa 4.
Ao validar a entrada do usuário, o CompareValidator relatará dados inválidos se o valor contiver um símbolo de moeda. Adicione um $ na frente de cada um dos TextBoxes de preço unitário para servir como uma indicação visual que instrui o usuário a omitir o símbolo de moeda ao inserir o preço.
Por fim, adicione um controle ValidationSummary no InsertingInterface
Painel, definindo sua ShowMessageBox
propriedade true
como e sua ShowSummary
propriedade como false
. Com essas configurações, se o usuário inserir um valor de preço unitário inválido, um asterisco aparecerá ao lado dos controles TextBox ofensivos e o ValidationSummary exibirá uma caixa de mensagem do lado do cliente que mostra a mensagem de erro que especificamos anteriormente.
Neste ponto, sua tela deve ser semelhante à Figura 10.
Figura 10: A interface de inserção agora inclui TextBoxes para os nomes e preços dos produtos (clique para exibir a imagem em tamanho real)
Em seguida, precisamos adicionar os botões Adicionar Produtos de Remessa e Cancelar à linha de rodapé. Arraste dois controles Button da Caixa de Ferramentas para o rodapé da interface de inserção, definindo as propriedades Buttons ID
como e CancelButton
e Text
como Adicionar Produtos de Remessa e Cancelar, respectivamente.AddProducts
Além disso, defina a propriedade s do CancelButton
CausesValidation
controle como false
.
Por fim, precisamos adicionar um controle Da Web rótulo que exibirá status mensagens para as duas interfaces. Por exemplo, quando um usuário adiciona com êxito uma nova remessa de produtos, queremos retornar à interface de exibição e exibir uma mensagem de confirmação. Se, no entanto, o usuário fornecer um preço para um novo produto, mas deixar de fora o nome do produto, precisaremos exibir uma mensagem de aviso, pois o ProductName
campo é necessário. Como precisamos que essa mensagem seja exibida para ambas as interfaces, coloque-a na parte superior da página fora dos Painéis.
Arraste um controle Web Rótulo da Caixa de Ferramentas para a parte superior da página no Designer. Defina a ID
propriedade como StatusLabel
, limpe a Text
propriedade e defina as Visible
propriedades e como .false
EnableViewState
Como vimos nos tutoriais anteriores, definir a EnableViewState
propriedade como false
nos permite alterar programaticamente os valores da propriedade Label s e fazer com que eles reverter automaticamente de volta para seus padrões no postback subsequente. Isso simplifica o código para mostrar uma mensagem de status em resposta a alguma ação do usuário que desaparece no postback subsequente. Por fim, defina a StatusLabel
propriedade do controle s CssClass
como Warning, que é o nome de uma classe CSS definida em Styles.css
que exibe o texto em uma fonte grande, itálica, em negrito e vermelha.
A Figura 11 mostra o Visual Studio Designer depois que o Rótulo foi adicionado e configurado.
Figura 11: Colocar o StatusLabel
controle acima dos dois controles de painel (clique para exibir a imagem em tamanho real)
Etapa 3: Alternar entre as interfaces de exibição e inserção
Neste ponto, concluímos a marcação para nossas interfaces de exibição e inserção, mas ainda ficamos com duas tarefas:
- Alternando entre as interfaces de exibição e inserção
- Adicionando os produtos na remessa ao banco de dados
Atualmente, a interface de exibição está visível, mas a interface de inserção está oculta. Isso ocorre porque a DisplayInterface
propriedade Panel s Visible
está definida true
como (o valor padrão), enquanto a InsertingInterface
propriedade Panel é Visible
definida false
como . Para alternar entre as duas interfaces, basta alternar o valor da propriedade de Visible
cada controle.
Queremos passar da interface de exibição para a interface de inserção quando o botão Processar Remessa de Produto for clicado. Portanto, crie um manipulador de eventos para este evento de Click
Botão que contém o seguinte código:
protected void ProcessShipment_Click(object sender, EventArgs e)
{
DisplayInterface.Visible = false;
InsertingInterface.Visible = true;
}
Esse código simplesmente oculta o DisplayInterface
Painel e mostra o InsertingInterface
Painel.
Em seguida, crie manipuladores de eventos para os controles Adicionar Produtos da Remessa e Cancelar Botão na interface de inserção. Quando um desses Botões é clicado, precisamos reverter de volta para a interface de exibição. Crie Click
manipuladores de eventos para ambos os controles Button para que eles chamem ReturnToDisplayInterface
, um método que adicionaremos momentaneamente. Além de ocultar o InsertingInterface
Painel e mostrar o DisplayInterface
Painel, o ReturnToDisplayInterface
método precisa retornar os controles da Web para seu estado de pré-edição. Isso envolve definir as propriedades DropDownLists SelectedIndex
como 0 e limpar as Text
propriedades dos controles TextBox.
Observação
Considere o que pode acontecer se não retornarmos os controles ao estado de pré-edição antes de retornarmos à interface de exibição. Um usuário pode clicar no botão Processar Remessa de Produto, inserir os produtos da remessa e, em seguida, clicar em Adicionar Produtos da Remessa. Isso adicionaria os produtos e devolveria o usuário à interface de exibição. Neste ponto, talvez o usuário queira adicionar outra remessa. Ao clicar no botão Processar Remessa de Produto, eles retornariam à interface de inserção, mas as seleções DropDownList e os valores de TextBox ainda seriam preenchidos com seus valores anteriores.
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;
}
Ambos os Click
manipuladores de eventos simplesmente chamam o ReturnToDisplayInterface
método , embora retornemos ao manipulador de eventos Adicionar Produtos da Remessa Click
na Etapa 4 e adicionaremos código para salvar os produtos. ReturnToDisplayInterface
começa retornando o Suppliers
e Categories
DropDownLists para suas primeiras opções. As duas constantes firstControlID
e lastControlID
marcam os valores de índice de controle inicial e final usados para nomear o nome do produto e o preço unitário TextBoxes na interface de inserção e são usados nos limites do for
loop que define as Text
propriedades dos controles TextBox de volta para uma cadeia de caracteres vazia. Por fim, as propriedades Panels são redefinidas para que a interface de inserção Visible
fique oculta e a interface de exibição seja mostrada.
Reserve um momento para testar esta página em um navegador. Ao visitar a página pela primeira vez, você deverá ver a interface de exibição, conforme mostrado na Figura 5. Clique no botão Processar Remessa de Produto. A página será postback e agora você deverá ver a interface de inserção, conforme mostrado na Figura 12. Clicar nos botões Adicionar Produtos da Remessa ou Cancelar retorna você para a interface de exibição.
Observação
Ao exibir a interface de inserção, reserve um momento para testar os CompareValidators no preço unitário TextBoxes. Você deverá ver um aviso da caixa de mensagem do lado do cliente ao clicar no botão Adicionar Produtos da Remessa com valores de moeda inválidos ou preços com um valor menor que zero.
Figura 12: a interface de inserção é exibida depois de clicar no botão Processar Remessa de Produto (clique para exibir a imagem em tamanho real)
Etapa 4: Adicionar os produtos
Tudo o que resta para este tutorial é salvar os produtos no banco de dados no manipulador de eventos Adicionar Produtos do Click
Botão de Remessa. Isso pode ser feito criando um ProductsDataTable
e adicionando uma instância para cada um ProductsRow
dos nomes de produtos fornecidos. Depois que esses ProductsRow
s forem adicionados, faremos uma chamada para o ProductsBLL
método da classe s UpdateWithTransaction
passando o ProductsDataTable
. Lembre-se de que o UpdateWithTransaction
método , que foi criado novamente no tutorial Encapsulando modificações de banco de dados dentro de uma transação , passa o ProductsDataTable
para o ProductsTableAdapter
método s UpdateWithTransaction
. A partir daí, uma transação ADO.NET é iniciada e o TableAdapter emite uma instrução INSERT
para o banco de dados para cada adicionado ProductsRow
na DataTable. Supondo que todos os produtos sejam adicionados sem erro, a transação será confirmada, caso contrário, será revertida.
O código para o manipulador de eventos Adicionar Produtos do Click
Botão de Remessa também precisa executar um pouco de verificação de erros. Como não há RequiredFieldValidators usados na interface de inserção, um usuário pode inserir um preço para um produto ao omitir seu nome. Como o nome do produto é necessário, se essa condição se desenrolar, precisamos alertar o usuário e não prosseguir com as inserções. O código completo Click
do manipulador de eventos segue:
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;
}
}
O manipulador de eventos começa garantindo que a Page.IsValid
propriedade retorne um valor de true
. Se retornar false
, isso significa que um ou mais compareValidators estão relatando dados inválidos; nesse caso, não queremos tentar inserir os produtos inseridos ou acabaremos com uma exceção ao tentar atribuir o valor de preço unitário inserido pelo usuário à ProductsRow
propriedade s UnitPrice
.
Em seguida, uma nova ProductsDataTable
instância é criada (products
). Um for
loop é usado para iterar pelo nome do produto e pelo preço unitário TextBoxes e as Text
propriedades são lidas productName
nas variáveis locais e unitPrice
. Se o usuário tiver inserido um valor para o preço unitário, mas não para o nome do produto correspondente, o StatusLabel
exibirá a mensagem Se você fornecer um preço unitário, também deverá incluir o nome do produto e o manipulador de eventos for encerrado.
Se um nome de produto tiver sido fornecido, uma nova ProductsRow
instância será criada usando o ProductsDataTable
método s NewProductsRow
. Essa nova ProductsRow
propriedade da instância é definida como o nome atual do ProductName
produto TextBox enquanto as SupplierID
propriedades e CategoryID
são atribuídas às SelectedValue
propriedades do DropDownLists no cabeçalho da interface de inserção. Se o usuário inseriu um valor para o preço do produto, ele será atribuído à ProductsRow
propriedade da UnitPrice
instância; caso contrário, a propriedade será deixada não atribuída, o que resultará em um NULL
valor para UnitPrice
no banco de dados. Por fim, as Discontinued
propriedades e UnitsOnOrder
são atribuídas aos valores embutidos false
em código e 0, respectivamente.
Depois que as propriedades tiverem sido atribuídas à ProductsRow
instância, ela será adicionada ao ProductsDataTable
.
Ao concluir o for
loop, marcar se algum produto foi adicionado. O usuário pode, afinal, ter clicado em Adicionar Produtos da Remessa antes de inserir nomes ou preços de produtos. Se houver pelo menos um produto no ProductsDataTable
, o ProductsBLL
método da classe s UpdateWithTransaction
será chamado. Em seguida, os dados são recuperados para o ProductsGrid
GridView para que os produtos recém-adicionados apareçam na interface de exibição. O StatusLabel
é atualizado para exibir uma mensagem de confirmação e o ReturnToDisplayInterface
é invocado, ocultando a interface de inserção e mostrando a interface de exibição.
Se nenhum produto tiver sido inserido, a interface de inserção permanecerá exibida, mas a mensagem Nenhum produto foi adicionado. Insira os nomes dos produtos e os preços unitários nas caixas de texto são exibidos.
As figuras 13, 14 e 15 mostram as interfaces de inserção e exibição em ação. Na Figura 13, o usuário inseriu um valor de preço unitário sem um nome de produto correspondente. A Figura 14 mostra a interface de exibição depois que três novos produtos foram adicionados com êxito, enquanto a Figura 15 mostra dois dos produtos recém-adicionados no GridView (o terceiro está na página anterior).
Figura 13: Um nome de produto é necessário ao inserir um preço unitário (clique para exibir a imagem em tamanho real)
Figura 14: Três novos vegetais foram adicionados para o fornecedor Mayumi s (clique para exibir a imagem em tamanho real)
Figura 15: Os novos produtos podem ser encontrados na última página do GridView (clique para exibir a imagem em tamanho real)
Observação
A lógica de inserção em lote usada neste tutorial encapsula as inserções no escopo da transação. Para verificar isso, introduza propositalmente um erro no nível do banco de dados. Por exemplo, em vez de atribuir a propriedade da CategoryID
nova ProductsRow
instância ao valor selecionado no DropDownList, atribua-a Categories
a um valor como i * 5
. Aqui i
está o indexador de loop e tem valores que variam de 1 a 5. Portanto, ao adicionar dois ou mais produtos na inserção em lote, o primeiro produto terá um valor válido CategoryID
(5), mas os produtos subsequentes terão CategoryID
valores que não correspondem aos CategoryID
valores na Categories
tabela. O efeito líquido é que, embora o primeiro INSERT
seja bem-sucedido, os subsequentes falharão com uma violação de restrição de chave estrangeira. Como a inserção em lote é atômica, a primeira INSERT
será revertida, retornando o banco de dados para seu estado antes do início do processo de inserção em lote.
Resumo
Neste e nos dois tutoriais anteriores, criamos interfaces que permitem atualizar, excluir e inserir lotes de dados, todos os quais usaram o suporte à transação que adicionamos à Camada de Acesso a Dados no tutorial Encapsulando modificações de banco de dados em um tutorial de transação . Para determinados cenários, essas interfaces de usuário de processamento em lotes melhoram muito a eficiência do usuário final reduzindo o número de cliques, postbacks e comutadores de contexto de teclado para mouse, mantendo também a integridade dos dados subjacentes.
Este tutorial conclui nossa análise de como trabalhar com dados em lote. O próximo conjunto de tutoriais explora uma variedade de cenários avançados da Camada de Acesso a Dados, incluindo o uso de procedimentos armazenados nos métodos TableAdapter, configurações de nível de conexão e comando no DAL, criptografia de cadeias de conexão e muito mais!
Programação feliz!
Sobre o autor
Scott Mitchell, autor de sete livros do ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.
Agradecimentos Especiais
Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores deste tutorial foram Hilton Giesenow e S ren Jacob Lauritsen. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, solte-me uma linha em mitchell@4GuysFromRolla.com.