Partilhar via


Inserção em lote (C#)

por Scott Mitchell

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

Introdução

No tutorial de atualização em lote, examinámos a personalização do control GridView para apresentar uma interface onde vários registos se tornavam editáveis. O usuário que visita a página pode fazer uma série de alterações e, em seguida, com um único clique no 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 comparada aos recursos de edição padrão por linha que foram explorados pela primeira vez no tutorial Uma visão geral da inserção, atualização e exclusão de dados .

Este conceito também pode ser aplicado ao adicionar registros. Imagine que aqui na Northwind Traders normalmente recebemos remessas de fornecedores que contêm uma série de produtos para uma determinada categoria. Como 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 através 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). Esta 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 ao usuário escolher o fornecedor e a categoria uma vez, inserir uma série de nomes de produtos e preços unitários e, em seguida, clicar em um botão para adicionar os novos produtos ao banco de dados (veja a Figura 1). À medida que cada produto é adicionado, os campos ProductName e os dados UnitPrice recebem os valores inseridos nos campos de texto, enquanto os valores CategoryID e SupplierID são atribuídos os valores das listas suspensas no topo do formulário. Os valores de Discontinued e UnitsOnOrder são definidos como false e 0, respectivamente.

A interface de inserção em lote

Figura 1: A interface de inserção em lote (Clique para visualizar a imagem em tamanho real)

Neste tutorial, criaremos uma página que implementa a interface de inserção em lote mostrada na Figura 1. Como nos dois tutoriais anteriores, vamos encapsular as inserções no âmbito 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 da remessa ou Cancelar são clicados. Criaremos a interface de inserção na Etapa 2.

Ao criar uma página que tem duas interfaces, apenas uma das quais é visível de cada vez, cada interface normalmente é colocada dentro de um controle Web Panel, que serve como um contêiner para outros controles. Portanto, nossa página terá dois controles de painel, um para cada interface.

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

Arraste um Painel da Caixa de Ferramentas para o Designer

Figura 2: Arraste um painel da caixa de ferramentas para o Designer (Clique para visualizar a imagem em tamanho real)

Em seguida, arraste um botão e o GridView para o Painel. Defina a propriedade Button s ID como ProcessShipment e sua Text propriedade como Process Product Shipment. Defina a propriedade do GridView ID como ProductsGrid e, a partir da sua marca inteligente, vincule-a a um novo ObjectDataSource chamado ProductsDataSource. Configure o ObjectDataSource para extrair os seus dados do método da classe ProductsBLL s GetProducts. Como este GridView é usado apenas para exibir dados, defina as listas suspensas nas guias ATUALIZAR, INSERIR e ELIMINAR como (Nenhum). Clique em Concluir para finalizar o assistente de Configuração da Fonte de Dados.

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

Figura 3: Exibir os dados retornados do método Class s ProductsBLL (GetProducts imagem em tamanho real)

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

Figura 4: Defina as listas de Drop-Down nas guias UPDATE, INSERT e DELETE como (Nenhuma) (Clique para visualizar 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 os ProductName, CategoryName, SupplierName, UnitPrice e Discontinued. Sinta-se livre para fazer quaisquer personalizações estéticas. Decidi formatar o UnitPrice campo como um valor de moeda, reordenei os campos e renomeei vários dos valores dos campos HeaderText . Configure também o GridView para incluir suporte a paginação e classificação, verificando as opções 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 de 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 aparecem dentro das tags de abertura e fechamento <asp:Panel> . Como esses controles estão dentro do DisplayInterface Painel, podemos ocultá-los simplesmente definindo a propriedade do Painel como Visiblefalse. A etapa 3 examina a alteração programática da propriedade do painel Visible em resposta a um clique de botão para mostrar uma interface enquanto esconde a outra.

Reserve um momento para ver o nosso progresso através de um navegador. Como mostra a Figura 5, você deve ver um botão Process Product Shipment acima de um GridView que lista os dez produtos de cada vez.

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

Figura 5: O GridView lista os produtos e oferece recursos de classificação e paginação (Clique para visualizar a imagem em tamanho real)

Etapa 2: Criando a interface de inserção

Com a interface de exibição completa, estamos prontos para criar a interface de inserção. Para este tutorial, vamos criar uma interface de inserção que solicita um único fornecedor e valor de 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 de um a cinco novos produtos que compartilham a mesma categoria e fornecedor, mas têm nomes e preços 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 o código que define a propriedade do InsertingInterface Panel Visible para true no Passo 3. Limpe também o painel e os valores das propriedades Height e Width.

Em seguida, precisamos criar a interface de inserção que foi mostrada na Figura 1. Esta interface pode ser criada através 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 a visualização Código-fonte. Embora o Visual Studio tenha ferramentas para adicionar <table> elementos por meio do Designer, o Designer parece inclinado a injetar configurações style não solicitadas na marcação. Depois de criar a <table> marcação, geralmente volto ao Designer para adicionar os controles da Web e definir suas propriedades. Ao criar tabelas com colunas e linhas pré-determinadas, prefiro usar HTML estático em vez do controle Table Web porque qualquer controle Web colocado dentro de um controle Table Web só pode ser acessado usando o FindControl("controlID") padrão. No entanto, eu uso controles Table Web para tabelas de tamanho dinâmico (aquelas cujas linhas ou colunas são baseadas em algum banco de dados ou critérios especificados pelo usuário), uma vez que o controle Table Web 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>

Este elemento <table> ainda não inclui nenhum controlo da Web, vamos adicioná-los em breve. Observe que cada elemento <tr> contém uma configuração de classe CSS específica: BatchInsertHeaderRow para a linha de cabeçalho onde irão os DropDownLists de fornecedor e categoria; BatchInsertFooterRow para a linha de rodapé onde os botões Adicionar Produtos da Remessa e Cancelar irão; e valores alternados BatchInsertRow e BatchInsertAlternatingRow para as linhas que conterão os controles TextBox de produto e 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 destes 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 à visualizaçã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 Seven-Row de quatro colunas

Figura 6: A interface de inserção é composta por uma tabela de Seven-Row de quatro colunas (Clique para visualizar 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, uma para o fornecedor e outra 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 os dados a partir do método SuppliersBLL da classe GetSuppliers e defina a lista suspensa da aba de atualização para (Nenhum). Clique em Concluir para finalizar o assistente.

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

Figura 7: Configurar o ObjectDataSource para usar o método Class s SuppliersBLL (GetSuppliers imagem em tamanho real)

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

Exiba o campo de dados CompanyName e use SupplierID como o valor

Figura 8: Exibir o CompanyName campo de dados e usar SupplierID como o valor (Clique para visualizar 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 guias UPDATE e DELETE como (Nenhum) e clique em Concluir para finalizar o assistente. Finalmente, peça ao DropDownList que exiba o CategoryName campo de dados e use o CategoryID como o valor.

Depois que essas duas DropDownLists tiverem sido adicionadas e vinculadas a ObjectDataSources configuradas adequadamente, sua tela deverá ser semelhante à Figura 9.

A linha de cabeçalho agora contém listas suspensas para Fornecedores e Categorias

Figura 9: A linha de cabeçalho agora contém as Suppliers e Categories listas suspensas (Clique para ver 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, um para cada uma das cinco linhas de nome e preço dos produtos. Defina as ID propriedades das TextBoxes como ProductName1, UnitPrice1, , ProductName2UnitPrice2, ProductName3, UnitPrice3e assim por diante.

Adicione um CompareValidator após cada um dos TextBoxes de preço unitário, definindo a propriedade ControlToValidate para a ID apropriada. 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 quaisquer símbolos de moeda.

Observação

A interface de inserção não inclui nenhuns controlos RequiredFieldValidator, mesmo que o campo ProductName na tabela de banco de dados Products não permita valores NULL. Isso porque queremos permitir que o usuário insira até cinco produtos. Por exemplo, se o usuário fornecer o nome do produto e o preço unitário para as três primeiras linhas, deixando as duas últimas linhas em branco, basta adicionar 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 relata dados inválidos se o valor contiver um símbolo de moeda. Adicione um $ na frente de cada uma das TextBoxes de preço unitário para servir como uma sugestã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 dentro do InsertingInterface Panel, defina sua ShowMessageBox propriedade para true e sua ShowSummary propriedade para 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 caixas de texto para os nomes e preços dos produtos (Clique para visualizar a imagem em tamanho real)

Em seguida, precisamos adicionar os botões Adicionar produtos da remessa e Cancelar à linha de rodapé. Arraste dois controles Botões da Caixa de Ferramentas para o rodapé da interface de inserção, definindo as propriedades dos Botões ID como AddProducts e CancelButton e Text como Adicionar Produtos da Remessa e Cancelar, respectivamente. Além disso, defina a propriedade CancelButton do controlo CausesValidation para false.

Finalmente, precisamos adicionar um controle Label Web 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, precisamos exibir uma mensagem de aviso, pois o ProductName campo é obrigatório. Como precisamos que essa mensagem seja exibida para ambas as interfaces, coloque-a no topo da página fora dos painéis.

Arraste um controle Web Label 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 EnableViewState como false. Como vimos em tutoriais anteriores, definir a propriedade EnableViewState para false permite-nos alterar programaticamente os valores da propriedade Label e fazer com que revertam automaticamente para os 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. Finalmente, defina a StatusLabel sua propriedade de controle CssClass como Aviso, que é o nome de uma classe CSS definida em Styles.css que exibe texto em uma fonte grande, itálico, negrito e vermelho.

A Figura 11 mostra o Visual Studio Designer após o Label ter sido 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 visualizar 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 restam duas tarefas:

  • Alternar entre o ecrã e inserir interfaces
  • Adicionando os produtos da remessa ao banco de dados

Atualmente, a interface de exibição é visível, mas a interface de inserção está oculta. Isso ocorre porque a DisplayInterface propriedade Panel s Visible está definida como true (o valor padrão), enquanto a InsertingInterface propriedade Panel s Visible está definida como false. Para alternar entre as duas interfaces, basta mudar o valor da propriedade Visible de cada controlo.

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

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

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

Em seguida, crie manipuladores de eventos para os controles Add Products from Shipment e Cancel Button na interface de inserção. Quando um desses botões é clicado, precisamos reverter para a interface de exibição. Crie Click manipuladores de eventos para ambos os controles de botões para que eles chamem ReturnToDisplayInterface, um método que adicionaremos em breve. Além de ocultar o InsertingInterface Painel e mostrar o DisplayInterface Painel, o ReturnToDisplayInterface método precisa retornar 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 seu estado de pré-edição antes de retornar à interface de exibição. Um usuário pode clicar no botão Processar Remessa de Produtos, inserir os produtos da remessa e clicar em Adicionar Produtos da Remessa. Isso adicionaria os produtos e retornaria o usuário à interface de exibição. Neste ponto, o usuário pode querer 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 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 Add Products from Shipment Click na Etapa 4 e adicionemos 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 as caixas de texto de nome do produto e preço unitário na interface de inserção e são usadas nos limites do loop for que redefine as propriedades Text dos controles de caixa de texto para uma cadeia de caracteres vazia. Finalmente, as propriedades de Panels Visible são redefinidas para que a interface de inserção fique oculta e a interface de exibição mostrada.

Reserve um momento para testar esta página em um navegador. Ao visitar a página pela primeira vez, você verá a interface de exibição, como mostrado na Figura 5. Clique no botão Processar envio de produto. A página será postback e agora você deve ver a interface de inserção como mostrado na Figura 12. Clicar nos botões Adicionar produtos da remessa ou Cancelar retorna você à interface de exibição.

Observação

Ao visualizar a interface de inserção, reserve um momento para testar os CompareValidators nas TextBoxes de preço unitário. Você verá um aviso de 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 valor inferior a zero.

A interface de inserção é exibida depois de clicar no botão Processar envio de produtos

Figura 12: A interface de inserção é exibida após clicar no botão Processar envio de produtos (Clique para visualizar a imagem em tamanho real)

Etapa 4: Adicionando os produtos

Tudo o que resta para este tutorial é salvar os produtos no banco de dados no manipulador de eventos Add Products from Shipment Button s Click . Isso pode ser feito criando uma ProductsDataTable e adicionando uma instância ProductsRow para cada um dos nomes de produtos fornecidos. Assim que estes ProductsRow s tiverem sido adicionados, faremos uma chamada ao método ProductsBLL da classe UpdateWithTransaction, passando 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 ProductsRow adicionado ao DataTable. Supondo que todos os produtos sejam adicionados sem erro, a transação é confirmada, caso contrário, é revertida.

O código para o manipulador de eventos Add Products from Shipment Button também Click 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 enquanto omite seu nome. Uma vez que o nome do produto é necessário, caso essa condição ocorra, devemos alertar o utilizador 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 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 é utilizado para iterar pelos campos de texto do nome do produto e do preço unitário, e as propriedades Text são lidas para as variáveis locais productName 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 será encerrado.

Se um nome de produto tiver sido fornecido, uma nova ProductsRow instância será criada usando o ProductsDataTable método s NewProductsRow . Esta nova ProductsRow instância tem a propriedade ProductName definida como o nome do produto atual na TextBox, enquanto as propriedades SupplierID e CategoryID são atribuídas às propriedades SelectedValue das listas drop-down no cabeçalho da interface de inserção. Se o utilizador introduzir um valor para o preço do produto, este será atribuído à propriedade ProductsRow da instância UnitPrice; caso contrário, a propriedade permanecerá sem atribuição, o que resultará num valor NULL para UnitPrice na base de dados. Finalmente, as propriedades Discontinued e UnitsOnOrder são atribuídas aos valores codificados false e 0, respectivamente.

Depois que as propriedades foram atribuídas à instância ProductsRow, ela é adicionada ao ProductsDataTable.

Ao concluir o for ciclo, verificamos se algum produto foi adicionado. O utilizador pode, afinal, ter clicado em Adicionar Produtos da Expedição antes de introduzir quaisquer nomes ou preços de produtos. Se houver pelo menos um produto no ProductsDataTable, o método da classe ProductsBLLUpdateWithTransaction é chamado. Em seguida, os dados são redirecionados 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 for inserido, a interface de inserção permanecerá exibida, mas a mensagem Nenhum produto foi adicionado. Introduza os nomes dos produtos e os preços unitários nas caixas de texto apresentadas.

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 após três novos produtos terem sido 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 visualizar 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 ver 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 visualizar a imagem em tamanho real)

Observação

A lógica de inserção em lote usada neste tutorial encapsula as inserções dentro do 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 s da nova instância ProductsRow ao valor selecionado na CategoryID DropDownList Categories, atribua-a 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 de 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 da Categories tabela. O efeito líquido é que, enquanto o primeiro INSERT será bem-sucedido, os seguintes irão falhar devido a 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 ao seu estado anterior ao início do processo de inserção em lote.

Resumo

Ao longo deste e dos dois tutoriais anteriores, criamos interfaces que permitem atualizar, excluir e inserir lotes de dados, todos os quais usaram o suporte a transações que adicionamos à camada de acesso a dados no tutorial Encapsulando modificações de banco de dados dentro de uma transação . Para determinados cenários, essas interfaces de usuário de processamento em lote melhoram consideravelmente a eficiência do usuário final, reduzindo o número de cliques, postbacks e opções de contexto de teclado para mouse, ao mesmo tempo em que mantêm a integridade dos dados subjacentes.

Este tutorial completa nossa análise sobre como trabalhar com dados em lotes. 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 nível de conexão e comando no DAL, a criptografia de cadeias de conexão e muito mais!

Feliz Programação!

Sobre o Autor

Scott Mitchell, autor de sete livros sobre ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias Web da Microsoft desde 1998. Scott trabalha como consultor, formador e escritor independente. Seu último livro é Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Ele pode ser contatado em mitchell@4GuysFromRolla.com.

Um agradecimento especial a

Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores para este tutorial foram Hilton Giesenow e S ren Jacob Lauritsen. Interessado em rever meus próximos artigos do MSDN? Se for o caso, envie-me uma mensagem para mitchell@4GuysFromRolla.com.