Compartilhar via


Inserção em lote (VB)

por Scott Mitchell

Baixar PDF

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 de Inserção em uma transação para garantir que todas as inserções sejam bem-sucedidas ou todas as inserções sejam revertidas.

Introdução

No tutorial sobre Atualização em Lote, analisamos como personalizar o controle GridView para apresentar uma interface em que vários registros podem ser editados. 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 geralmente atualizam muitos registros de uma só vez, essa interface pode salvar inúmeros cliques e opções de contexto de teclado para mouse quando comparadas com os recursos padrão de edição por linha que foram explorados pela primeira vez novamente no tutorial Uma Visão Geral de Inserir, Atualizar e Excluir Dados .

Esse conceito também pode ser aplicado ao adicionar registros. Imagine que aqui na Northwind Traders geralmente recebemos remessas de fornecedores que contêm uma série de 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 e UnitPrice campos de dados são atribuídos aos valores inseridos nas caixas de texto, enquanto seus valores CategoryID e SupplierID são atribuídos aos valores das listas suspensas na parte superior do formulário. Os valores Discontinued e UnitsOnOrder são definidos como os valores fixos de False e 0, respectivamente.

A interface de inserção em lote

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 no 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 Process Product Shipment. 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, uma só fica visível por vez, cada interface normalmente é colocada dentro de um controle de painel web, que serve como um contêiner para outros controles. Portanto, nossa página terá dois controles de painel um para cada interface.

Comece abrindo a página BatchInsert.aspx na pasta BatchData e arraste um Painel da Caixa de Ferramentas para o Designer (ver Figura 2). Defina a propriedade ID do Painel como DisplayInterface. Ao adicionar o Painel ao Designer, suas propriedades Height e Width são definidas como 50px e 125px, respectivamente. Desmarque esses valores de propriedade da janela Propriedades.

Arrastar um painel da caixa de ferramentas para o Designer

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 ID do Botão como ProcessShipment e sua propriedade Text para Processar Remessa de Produto. Defina a propriedade ID do GridView como ProductsGrid e, de sua marca inteligente, associe-a a um novo ObjectDataSource chamado ProductsDataSource. Configure o ObjectDataSource para extrair seus dados do ProductsBLL método da GetProducts classe. Como esse GridView é usado apenas para exibir dados, defina as listas suspensas nas guias UPDATE, INSERT e DELETE como (Nenhum). Clique em Concluir para finalizar o assistente Configurar Fonte de Dados.

Exibir os dados retornados do método GetProducts da classe ProductsBLL

Figura 3: Exibir os dados retornados do ProductsBLL método da GetProducts classe (clique para exibir a imagem em tamanho real)

Definir as listas de Drop-Down nas guias UPDATE, INSERT e DELETE como (Nenhuma)

Figura 4: Definir as listas de Drop-Down 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 os campos, exceto ProductName, CategoryName, SupplierName, UnitPrice e Discontinued. Fique à 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 . Também configure o GridView para incluir suporte de paginação e classificação, verificando as caixas de seleção Habilitar Paginação e Habilitar Classificação na marca inteligente do 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 do Button e do GridView aparece entre as tags de abertura e fechamento <asp:Panel>. Como esses controles estão dentro do DisplayInterface Painel, podemos ocultá-los simplesmente definindo a propriedade do Visible Painel como False. A etapa 3 analisa a alteração programática da propriedade do Painel Visible 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 Envio de Produto acima de um GridView que lista os produtos dez de cada vez.

O GridView lista os produtos e oferece recursos de classificação e paginação

Figura 5: O GridView lista 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 preços e nomes de produtos exclusivos.

Comece arrastando um Painel da Caixa de Ferramentas para o Designer, colocando-o abaixo do Painel existente DisplayInterface . Defina a propriedade ID deste Painel recém-adicionado para InsertingInterface e defina sua propriedade Visible como False. Adicionaremos um código que define a InsertingInterface propriedade Visible do True Painel na Etapa 3. Limpe também os valores de propriedade Height e Width do Painel.

Em seguida, precisamos criar a interface de inserção mostrada 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 elementos <table> por meio do Designer, o Designer parece muito propenso a injetar configurações style indesejadas 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 de Tabela Web, pois todos os controles Web colocados em um controle de Tabela Web 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> tags 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 adicionar em breve. Observe que o elemento <tr> contém uma configuração de classe CSS específica: BatchInsertHeaderRow para a linha de cabeçalho onde os DropDownLists de fornecedor e categoria serão posicionados; BatchInsertFooterRow para a linha de rodapé onde os botões Adicionar Produtos ao Remessa e Cancelar serão posicionados; e alternando valores BatchInsertRow e BatchInsertAlternatingRow para as linhas que conterão os controles TextBox de produto e de preço unitário. 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 de exibição Design. Isso <table> deve ser mostrado como uma tabela de quatro colunas e sete linhas no Designer, como ilustra a Figura 6.

A interface de inserção é composta por uma tabela de quatro colunas Seven-Row

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 na interface de inserção. Arraste dois DropDownLists da Caixa de Ferramentas para as células apropriadas na tabela, sendo um para o fornecedor e outro para a categoria.

Defina a propriedade ID do DropDownList do fornecedor para Suppliers e associe-a a um novo ObjectDataSource chamado SuppliersDataSource. Configure o novo ObjectDataSource para recuperar seus dados do método da classe SuppliersBLLGetSuppliers e defina a lista suspensa da guia UPDATE como (Nenhum). Clique em "Finalizar" para completar o assistente.

Configurar o ObjectDataSource para usar o método GetSuppliers da classe SuppliersBLL

Figura 7: Configurar o ObjectDataSource para usar o SuppliersBLL método da GetSuppliers classe (clique para exibir a imagem em tamanho real)

Faça com que o Suppliers DropDownList exiba o campo de dados CompanyName e utilize o campo de dados SupplierID como os seus valores ListItem.

Exiba o campo de dados CompanyName e use SupplierID como valor

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 método CategoriesBLL da classe GetCategories; defina as listas suspensas nas abas UPDATE e DELETE como (Nenhum) e clique em Concluir para finalizar 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.

A linha de cabeçalho agora contém os fornecedores e categorias DropDownLists

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 o Designer para cada uma das cinco linhas de nome e preço do produto. Defina as ID propriedades dos TextBoxes como ProductName1, , UnitPrice1, ProductName2, UnitPrice2, ProductName3e UnitPrice3assim por diante.

Adicione um CompareValidator após cada um dos TextBoxes de preço unitário, definindo a ControlToValidate propriedade como a apropriada ID. Defina também a Operator propriedade como GreaterThanEqual, ValueToCompare para 0 e Type para 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 propriedade Text 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 de RequiredFieldValidator, embora o campo ProductName na tabela de banco de dados Products não permita valores NULL. 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 verificar programaticamente para garantir que, se um preço unitário for inserido, um valor de nome de produto correspondente seja fornecido. Abordaremos essa verificação 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 a propriedade ShowMessageBox como True e a propriedade ShowSummary 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 especificada anteriormente.

Neste ponto, sua tela deve ser semelhante à Figura 10.

A interface de inserção agora inclui TextBoxes para os nomes e preços dos produtos

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 do rodapé. Arraste dois controles Botão da caixa de ferramentas para o rodapé da interface de inserção, definindo as propriedades dos Botões ID para AddProducts e as propriedades CancelButton e Text para Adicionar Produtos de Remessa e Cancelar, respectivamente. Além disso, defina a CancelButton propriedade do CausesValidation controle como false.

Por fim, precisamos adicionar um controle Web de rótulo que exibirá mensagens de status 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 de Rótulo da Web da Caixa de Ferramentas até o topo da página no Designer. Defina a propriedade ID como StatusLabel, limpe a propriedade Text e defina as propriedades Visible e EnableViewState como False. Como vimos em tutoriais anteriores, definir a propriedade EnableViewState para False permite-nos alterar programaticamente os valores de propriedade dos Rótulos e fazer com que revertam automaticamente 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 propriedade do controle StatusLabel como Warning, que é o nome de uma classe CSS, definida em CssClass, que exibe o texto em uma fonte vermelha, grande, em itálico e negrito.

A Figura 11 mostra o Designer do Visual Studio depois que o Rótulo foi adicionado e configurado.

Coloque o controle StatusLabel acima dos dois controles de painel

Figura 11: Coloque 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 de inserção

Neste ponto, concluímos a marcação para nossas interfaces de exibição e inserção, mas ainda ficamos com duas tarefas:

  • Alternar 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 do Visible Painel é definida True como (o valor padrão), enquanto a InsertingInterface propriedade do Visible Painel é definida como False. Para alternar entre as duas interfaces, basta ajustar o valor da propriedade Visible de cada controle.

Queremos passar da interface de exibição para a interface de inserção quando o botão Processar Envio de Produto for clicado. Portanto, crie um manipulador de eventos para este evento do Click Botão que contém o seguinte código:

Protected Sub ProcessShipment_Click(sender As Object, e As EventArgs) _
    Handles ProcessShipment.Click
    DisplayInterface.Visible = False
    InsertingInterface.Visible = True
End Sub

Esse código simplesmente oculta o DisplayInterface Painel e mostra o InsertingInterface Painel.

Em seguida, crie manipuladores de eventos para os controles Adicionar Produtos do Botão de Envio e Cancelamento 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 de botão de modo que eles chamem ReturnToDisplayInterface, um método que adicionaremos em breve. Além de ocultar o InsertingInterface Painel e mostrar o DisplayInterface Painel, o método ReturnToDisplayInterface precisa restaurar os controles da Web ao 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 Envio de Produto, inserir os produtos da remessa e, em seguida, clicar em Adicionar Produtos da Remessa. Isso adicionaria os produtos e retornaria o usuário à interface de exibição. Neste ponto, talvez o usuário queira adicionar outra remessa. Ao clicar no botão Processar Envio 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 Sub AddProducts_Click(sender As Object, e As EventArgs) _
    Handles AddProducts.Click
    ' TODO: Save the products
    ' Revert to the display interface
    ReturnToDisplayInterface()
End Sub
Protected Sub CancelButton_Click(sender As Object, e As EventArgs) _
    Handles CancelButton.Click
    ' Revert to the display interface
    ReturnToDisplayInterface()
End Sub
Const firstControlID As Integer = 1
Const lastControlID As Integer = 5
Private Sub ReturnToDisplayInterface()
    ' Reset the control values in the inserting interface
    Suppliers.SelectedIndex = 0
    Categories.SelectedIndex = 0
    For i As Integer = firstControlID To lastControlID
        CType(InsertingInterface.FindControl _
            ("ProductName" + i.ToString()), TextBox).Text = String.Empty
        CType(InsertingInterface.FindControl _
            ("UnitPrice" + i.ToString()), TextBox).Text = String.Empty
    Next
    DisplayInterface.Visible = True
    InsertingInterface.Visible = False
End Sub

Ambos os Click manipuladores de eventos simplesmente chamam o ReturnToDisplayInterface método, embora retornaremos 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 na nomenclatura das caixas de texto do nome do produto e do preço unitário na interface de entrada e são usadas nos limites do loop For que restabelece as propriedades de Text dos controles TextBox para uma cadeia de caracteres vazia. Por fim, as propriedades de Painéis 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 Envio do Produto. A página realizará um postback, e agora, você deverá ver a interface de inserção, conforme mostrado na Figura 12. Ao clicar nos botões Adicionar Produtos à Remessa ou Cancelar, você retorna para a interface de exibição.

Observação

Ao visualizar a interface de inserção, reserve um momento para testar os CompareValidators nas caixas de texto de preço unitário. Você deverá ver uma mensagem de aviso no lado do cliente ao clicar no botão Adicionar Produtos da Remessa quando houver valores de moeda inválidos ou preços com um valor menor que zero.

A interface de inserção é exibida depois de clicar no botão Processar Envio de Produto

Figura 12: A interface de inserção é exibida depois de clicar no botão De envio de produto do processo (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 ao acionar o evento do botão Adicionar Produtos da Remessa Click. Isso pode ser feito criando e adicionando uma ProductsDataTable instância para cada um ProductsRow dos nomes de produto fornecidos. Depois que estes ProductsRow tiverem sido adicionados, faremos uma chamada para o método da classe ProductsBLL passando UpdateWithTransaction. Lembre-se de que o método UpdateWithTransaction, que foi criado anteriormente no tutorial Encapsulando Modificações de Banco de Dados em uma Transação, passa o ProductsDataTable para o método ProductsTableAdapter do 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 entrada adicionada ProductsRow na DataTable. Supondo que todos os produtos sejam adicionados sem erros, a transação será confirmada, caso contrário, ela será revertida.

O código para o manipulador de eventos do Botão de Remessa Adicionar Produtos também precisa executar um pouco de verificação de erros. Como não há validadores de campos obrigatórios 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 Sub AddProducts_Click(sender As Object, e As EventArgs) _
    Handles AddProducts.Click
    ' Make sure that the UnitPrice CompareValidators report valid data...
    If Not Page.IsValid Then Exit Sub
    ' Add new ProductsRows to a ProductsDataTable...
    Dim products As New Northwind.ProductsDataTable()
    For i As Integer = firstControlID To lastControlID
        ' Read in the values for the product name and unit price
        Dim productName As String = CType(InsertingInterface.FindControl _
            ("ProductName" + i.ToString()), TextBox).Text.Trim()
        Dim unitPrice As String = CType(InsertingInterface.FindControl _
            ("UnitPrice" + i.ToString()), TextBox).Text.Trim()
        ' Ensure that if unitPrice has a value, so does productName
        If unitPrice.Length > 0 AndAlso productName.Length = 0 Then
            ' 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
            Exit Sub
        End If
        ' Only add the product if a product name value is provided
        If productName.Length > 0 Then
            ' Add a new ProductsRow to the ProductsDataTable
            Dim newProduct As Northwind.ProductsRow = 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 Then
                newProduct.UnitPrice = Convert.ToDecimal(unitPrice)
            End If
            ' Add any "default" values
            newProduct.Discontinued = False
            newProduct.UnitsOnOrder = 0
            products.AddProductsRow(newProduct)
        End If
    Next
    ' If we reach here, see if there were any products added
    If products.Count > 0 Then
        ' Add the new products to the database using a transaction
        Dim productsAPI As 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
    End If
End Sub

O manipulador de eventos começa garantindo que a Page.IsValid propriedade retorne um valor de True. Se ele retornar False, isso significa que um ou mais dos 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 pelas caixas de texto de nome do produto e preço unitário, e as propriedades Text são lidas para as variáveis locais productName e unitPrice. Se o usuário inseriu 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 será finalizado.

Se um nome de produto tiver sido fornecido, uma nova ProductsRow instância será criada usando o ProductsDataTable método s NewProductsRow . Esta nova propriedade de instância ProductsRow é definida como a TextBox do nome do produto atual, enquanto as propriedades ProductName e SupplierID são atribuídas às propriedades CategoryID dos 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 à propriedade da instância ProductsRowUnitPrice; caso contrário, a propriedade permanecerá não atribuída, resultando em um valor NULL no banco de dados UnitPrice. Por fim, as propriedades Discontinued e UnitsOnOrder são atribuídas aos valores embutidos em código, False e 0, respectivamente.

Depois que as propriedades tiverem sido atribuídas à ProductsRow instância, ela será adicionada ao ProductsDataTable.

Ao concluir o For loop, verificamos 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 em ProductsDataTable, o método ProductsBLL da classe 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. StatusLabel foi atualizado para exibir uma mensagem de confirmação e 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 exibidas na tela.

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).

Um nome de produto é necessário ao inserir um preço unitário

Figura 13: Um nome de produto é necessário ao inserir um preço unitário (clique para exibir a imagem em tamanho real)

Três novos vegetais foram adicionados para o fornecedor Mayumi s

Figura 14: Três novos vegetais foram adicionados para o fornecedor Mayumi s (clique para exibir a imagem em tamanho real)

Os novos produtos podem ser encontrados na última página do GridView

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 ProductsRow nova instância ao valor selecionado no CategoryID DropDownList, atribua-a a um valor como Categories. 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 resultado final é que, embora o primeiro INSERT tenha êxito, os subsequentes falharão devido a uma violação da restrição de chave estrangeira. Como a inserção em lote é atômica, a primeira INSERT será revertida, retornando o banco de dados ao seu estado anterior ao 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, utilizando sempre o suporte à transação que adicionamos à Camada de Acesso a Dados no tutorial Encapsulando Modificações no Banco de Dados Dentro de uma Transação. Para determinados cenários, essas interfaces de usuário de processamento em lote melhoram muito a eficiência do usuário final, reduzindo o número de cliques, postbacks e comutadores de contexto teclado a mouse, mantendo também a integridade dos dados subjacentes.

Este tutorial conclui nossa análise sobre 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 do TableAdapter, a configuração de configurações de conexão e nível de comando no DAL, criptografia de cadeias de conexão e muito mais!

Divirta-se programando!

Sobre o autor

Scott Mitchell, autor de sete livros 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 alcançado em mitchell@4GuysFromRolla.com.

Agradecimentos Especiais a

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? Se assim for, deixe-me uma linha em mitchell@4GuysFromRolla.com.