Exclusão em lote (C#)

por Scott Mitchell

Baixar PDF

Saiba como excluir vários registros de banco de dados em uma única operação. Na Camada de Interface do Usuário, criamos um GridView aprimorado criado em um tutorial anterior. Na Camada de Acesso a Dados, encapsulamos as várias operações delete em uma transação para garantir que todas as exclusões sejam bem-sucedidas ou todas as exclusões sejam revertidas.

Introdução

O tutorial anterior explorou como criar uma interface de edição em lote usando um GridView totalmente editável. Em situações em que os usuários normalmente editam muitos registros de uma só vez, uma interface de edição em lote exigirá muito menos postbacks e comutadores de contexto de teclado para mouse, melhorando assim a eficiência do usuário final. Essa técnica é igualmente útil para páginas em que é comum que os usuários excluam muitos registros de uma só vez.

Qualquer pessoa que tenha usado um cliente de email online já está familiarizada com uma das interfaces de exclusão em lote mais comuns: uma caixa de seleção em cada linha em uma grade com um botão Excluir Todos os Itens Verificados correspondente (consulte a Figura 1). Este tutorial é bastante curto porque já fizemos todo o trabalho árduo em tutoriais anteriores na criação da interface baseada na Web e um método para excluir uma série de registros como uma única operação atômica. No tutorial Adicionando uma coluna GridView de caixas de seleção, criamos um GridView com uma coluna de caixas de seleção e, no tutorial Encapsulando modificações de banco de dados em uma transação , criamos um método na BLL que usaria uma transação para excluir um List<T> de ProductID valores. Neste tutorial, vamos criar e mesclar nossas experiências anteriores para criar um exemplo de exclusão em lotes de trabalho.

Cada linha inclui uma caixa de seleção

Figura 1: cada linha inclui uma caixa de seleção (clique para exibir a imagem em tamanho real)

Etapa 1: Criando a interface de exclusão em lote

Como já criamos a interface de exclusão em lote no tutorial Adicionando uma coluna GridView de caixas de seleção , podemos simplesmente copiá-la para BatchDelete.aspx em vez de criá-la do zero. Comece abrindo a BatchDelete.aspx página na BatchData pasta e na CheckBoxField.aspx página na EnhancedGridView pasta . CheckBoxField.aspx Na página, vá para o modo de exibição Origem e copie a marcação entre as <asp:Content> marcas, conforme mostrado na Figura 2.

Copiar a marcação declarativa de CheckBoxField.aspx para a área de transferência

Figura 2: Copiar a Marcação Declarativa de CheckBoxField.aspx para a Área de Transferência (Clique para exibir a imagem em tamanho real)

Em seguida, vá para o modo de exibição Origem em BatchDelete.aspx e cole o conteúdo da área de transferência dentro das <asp:Content> marcas. Copie e cole também o código de dentro da classe code-behind em CheckBoxField.aspx.cs para dentro da classe code-behind em BatchDelete.aspx.cs (o DeleteSelectedProducts manipulador de eventos do Click Botão, o ToggleCheckState método e os Click manipuladores de eventos para os CheckAll botões e UncheckAll ). Depois de copiar esse conteúdo, a BatchDelete.aspx classe code-behind da página deve conter o seguinte código:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class BatchData_BatchDelete : System.Web.UI.Page
{
    protected void DeleteSelectedProducts_Click(object sender, EventArgs e)
    {
        bool atLeastOneRowDeleted = false;
        // Iterate through the Products.Rows property
        foreach (GridViewRow row in Products.Rows)
        {
            // Access the CheckBox
            CheckBox cb = (CheckBox)row.FindControl("ProductSelector");
            if (cb != null && cb.Checked)
            {
                // Delete row! (Well, not really...)
                atLeastOneRowDeleted = true;
                // First, get the ProductID for the selected row
                int productID = Convert.ToInt32(Products.DataKeys[row.RowIndex].Value);
                // "Delete" the row
                DeleteResults.Text += string.Format
                    ("This would have deleted ProductID {0}<br />", productID);
                //... To actually delete the product, use ...
                //ProductsBLL productAPI = new ProductsBLL();
                //productAPI.DeleteProduct(productID);
                //............................................
            }
        }
        // Show the Label if at least one row was deleted...
        DeleteResults.Visible = atLeastOneRowDeleted;
    }
    private void ToggleCheckState(bool checkState)
    {
        // Iterate through the Products.Rows property
        foreach (GridViewRow row in Products.Rows)
        {
            // Access the CheckBox
            CheckBox cb = (CheckBox)row.FindControl("ProductSelector");
            if (cb != null)
                cb.Checked = checkState;
        }
    }
    protected void CheckAll_Click(object sender, EventArgs e)
    {
        ToggleCheckState(true);
    }
    protected void UncheckAll_Click(object sender, EventArgs e)
    {
        ToggleCheckState(false);
    }
}

Depois de copiar a marcação declarativa e o código-fonte, reserve um momento para testar BatchDelete.aspx exibindo-o por meio de um navegador. Você deverá ver um GridView listando os dez primeiros produtos em um GridView com cada linha listando o nome, a categoria e o preço do produto junto com uma caixa de seleção. Deve haver três botões: Verificar Tudo, Desmarcar Todos e Excluir Produtos Selecionados. Clicar no botão Verificar Tudo marca todas as caixas de seleção, enquanto Desmarcar Tudo desmarca todas as caixas de seleção. Clicar em Excluir Produtos Selecionados exibe uma mensagem que lista os ProductID valores dos produtos selecionados, mas não exclui os produtos.

A interface do CheckBoxField.aspx foi movida para BatchDeleting.aspx

Figura 3: A interface de CheckBoxField.aspx foi movida para BatchDeleting.aspx (clique para exibir a imagem em tamanho real)

Etapa 2: Excluindo os produtos verificados usando transações

Com a interface de exclusão em lote copiada com êxito para BatchDeleting.aspx, tudo o que resta é atualizar o código para que o botão Excluir Produtos Selecionados exclua os produtos verificados usando o DeleteProductsWithTransaction método na ProductsBLL classe . Esse método, adicionado no tutorial Encapsulando modificações de banco de dados em uma transação , aceita como entrada um List<T> de ProductID valores e exclui cada correspondente ProductID dentro do escopo de uma transação.

Atualmente DeleteSelectedProducts , o manipulador de eventos Button Click usa o seguinte foreach loop para iterar em cada linha gridView:

// Iterate through the Products.Rows property
foreach (GridViewRow row in Products.Rows)
{
    // Access the CheckBox
    CheckBox cb = (CheckBox)row.FindControl("ProductSelector");
    if (cb != null && cb.Checked)
    {
        // Delete row! (Well, not really...)
        atLeastOneRowDeleted = true;
        // First, get the ProductID for the selected row
        int productID = Convert.ToInt32(Products.DataKeys[row.RowIndex].Value);
        // "Delete" the row
        DeleteResults.Text += string.Format
            ("This would have deleted ProductID {0}<br />", productID);
        //... To actually delete the product, use ...
        //ProductsBLL productAPI = new ProductsBLL();
        //productAPI.DeleteProduct(productID);
        //............................................
    }
}

Para cada linha, o ProductSelector controle Web CheckBox é referenciado programaticamente. Se estiver marcada, as linhas serão ProductID recuperadas da DataKeys coleção e a DeleteResults propriedade Label s Text será atualizada para incluir uma mensagem indicando que a linha foi selecionada para exclusão.

O código acima não exclui nenhum registro, pois a chamada para o ProductsBLL método da classe é Delete comentada. Se essa lógica de exclusão fosse aplicada, o código excluiria os produtos, mas não dentro de uma operação atômica. Ou seja, se as primeiras exclusões na sequência tiverem êxito, mas uma posterior falhar (talvez devido a uma violação de restrição de chave estrangeira), uma exceção será gerada, mas esses produtos já excluídos permanecerão excluídos.

Para garantir a atomicidade, precisamos usar o ProductsBLL método da classe s DeleteProductsWithTransaction . Como esse método aceita uma lista de ProductID valores, precisamos primeiro compilar essa lista da grade e, em seguida, passá-la como um parâmetro. Primeiro, criamos uma instância de um List<T> do tipo int. Dentro do foreach loop, precisamos adicionar os valores de produtos ProductID selecionados a este List<T>. Após o loop, isso List<T> deve ser passado para o ProductsBLL método da classe s DeleteProductsWithTransaction . Atualize o DeleteSelectedProducts manipulador de eventos do Click Botão com o seguinte código:

protected void DeleteSelectedProducts_Click(object sender, EventArgs e)
{
    // Create a List to hold the ProductID values to delete
    System.Collections.Generic.List<int> productIDsToDelete = 
        new System.Collections.Generic.List<int>();
    // Iterate through the Products.Rows property
    foreach (GridViewRow row in Products.Rows)
    {
        // Access the CheckBox
        CheckBox cb = (CheckBox)row.FindControl("ProductSelector");
        if (cb != null && cb.Checked)
        {
            // Save the ProductID value for deletion
            // First, get the ProductID for the selected row
            int productID = Convert.ToInt32(Products.DataKeys[row.RowIndex].Value);
            // Add it to the List...
            productIDsToDelete.Add(productID);
            // Add a confirmation message
            DeleteResults.Text += string.Format
                ("ProductID {0} has been deleted<br />", productID);
        }
    }
    // Call the DeleteProductsWithTransaction method and show the Label 
    // if at least one row was deleted...
    if (productIDsToDelete.Count > 0)
    {
        ProductsBLL productAPI = new ProductsBLL();
        productAPI.DeleteProductsWithTransaction(productIDsToDelete);
        DeleteResults.Visible = true;
        // Rebind the data to the GridView
        Products.DataBind();
    }
}

O código atualizado cria um List<T> do tipo int (productIDsToDelete) e o preenche com os ProductID valores a serem excluídos. Após o foreach loop, se houver pelo menos um produto selecionado, o ProductsBLL método da classe s DeleteProductsWithTransaction será chamado e aprovado nessa lista. O DeleteResults Rótulo também é exibido e os dados se recuperam para o GridView (para que os registros recém-excluídos não apareçam mais como linhas na grade).

A Figura 4 mostra o GridView depois que várias linhas foram selecionadas para exclusão. A Figura 5 mostra a tela imediatamente após o botão Excluir Produtos Selecionados ter sido clicado. Observe que, na Figura 5, os ProductID valores dos registros excluídos são exibidos no Rótulo abaixo de GridView e essas linhas não estão mais no GridView.

Os produtos selecionados serão excluídos

Figura 4: Os produtos selecionados serão excluídos (clique para exibir a imagem em tamanho real)

Os valores productID de produtos excluídos são listados abaixo do GridView

Figura 5: Os valores de produtos ProductID excluídos são listados abaixo do GridView (clique para exibir a imagem em tamanho real)

Observação

Para testar a DeleteProductsWithTransaction atomicidade do método, adicione manualmente uma entrada para um produto na Order Details tabela e tente excluir esse produto (juntamente com outros). Você receberá uma violação de restrição de chave estrangeira ao tentar excluir o produto com um pedido associado, mas observe como as exclusões de outros produtos selecionados são revertidas.

Resumo

A criação de uma interface de exclusão em lotes envolve a adição de um GridView com uma coluna de caixas de seleção e um controle Web button que, quando clicado, excluirá todas as linhas selecionadas como uma única operação atômica. Neste tutorial, criamos essa interface juntando o trabalho feito em dois tutoriais anteriores, adicionando uma coluna GridView de caixas de seleção e encapsulando modificações de banco de dados em uma transação. No primeiro tutorial, criamos um GridView com uma coluna de caixas de seleção e, no último, implementamos um método na BLL que, quando passado um List<T> de ProductID valores, excluía todos eles dentro do escopo de uma transação.

No próximo tutorial, criaremos uma interface para executar inserções em lote.

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 Teresa Murphy. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, deixe-me uma linha em mitchell@4GuysFromRolla.com.