Eliminación por lotes (VB)

por Scott Mitchell

Descargar PDF

Aprenda a eliminar varios registros de base de datos en una misma operación. En la capa de interfaz de usuario compilamos a partir de una clase GridView mejorada creada en un tutorial anterior. En la capa de acceso a datos, se encapsulan las diversas operaciones de inserción dentro de una transacción para garantizar que todas las inserciones se realizan correctamente o que todas se revierten.

Introducción

En el tutorial anterior se ha explorado cómo crear una interfaz de edición por lotes mediante GridView totalmente editable. En situaciones en las que los usuarios suelen editar muchos registros a la vez, una interfaz de edición por lotes requerirá muchos menos postback y modificadores de contexto de teclado a mouse, con lo cual mejorará la eficacia del usuario final. Esta técnica es útil de forma similar para las páginas en las que es habitual que los usuarios eliminen muchos registros de una sola vez.

Cualquier usuario que haya usado un cliente de correo electrónico en línea ya estará familiarizado con una de las interfaces de eliminación por lotes más comunes: una casilla en cada fila de una cuadrícula con un botón Eliminar todos los elementos activados correspondiente (vea la figura 1). Este tutorial es bastante corto porque ya hemos realizado todo el trabajo complejo en tutoriales anteriores, para crear la interfaz basada en web y un método para eliminar una serie de registros como una sola operación atómica. En el tutorial Agregar una columna GridView de casillas hemos creado una clase GridView con una columna de casillas y en el tutorial Ajustar modificaciones de base de datos en una transacción hemos creado un método en BLL que usará una transacción para eliminar List<T> de valores ProductID. En este tutorial, crearemos y combinaremos nuestras experiencias anteriores para crear un ejemplo de eliminación por lotes en funcionamiento.

Each Row Includes a Checkbox

Figura 1: Cada fila incluye una casilla (haga clic para ver la imagen a tamaño completo)

Paso 1: Creación de la interfaz de eliminación por lotes

Puesto que ya hemos creado la interfaz de eliminación por lotes en el tutorial Agregar una columna GridView de casillas, podemos copiarla en BatchDelete.aspx en lugar de crearla desde cero. Para empezar, abra la página BatchDelete.aspx en la carpeta BatchData y la página CheckBoxField.aspx en la carpeta EnhancedGridView. En la página CheckBoxField.aspx, vaya a la vista de Origen y copie el marcado entre las etiquetas <asp:Content>, como se muestra en la figura 2.

Copy the Declarative Markup of CheckBoxField.aspx to the Clipboard

Figura 2: Copiar el marcado declarativo de CheckBoxField.aspx en el Portapapeles (haga clic para ver la imagen a tamaño completo)

A continuación, vaya a la vista de Origen en BatchDelete.aspx y pegue el contenido del Portapapeles dentro de las etiquetas <asp:Content>. Copie y pegue también el código desde dentro de la clase de código subyacente en CheckBoxField.aspx.vb dentro de la clase de código subyacente en BatchDelete.aspx.vb (el controlador de eventos Click del botón DeleteSelectedProducts, el método ToggleCheckState y los controladores de eventos Click para los botones CheckAll y UncheckAll). Después de copiar este contenido, la clase de código subyacente de la página BatchDelete.aspx debe contener el código siguiente:

Partial Class BatchData_BatchDelete
    Inherits System.Web.UI.Page
    Protected Sub DeleteSelectedProducts_Click(sender As Object, e As EventArgs) _
        Handles DeleteSelectedProducts.Click
        
        Dim atLeastOneRowDeleted As Boolean = False
        ' Iterate through the Products.Rows property
        For Each row As GridViewRow In Products.Rows
            ' Access the CheckBox
            Dim cb As CheckBox = row.FindControl("ProductSelector")
            If cb IsNot Nothing AndAlso cb.Checked Then
                ' Delete row! (Well, not really...)
                atLeastOneRowDeleted = True
                ' First, get the ProductID for the selected row
                Dim productID As Integer = _
                    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 ...
                ' Dim productAPI As New ProductsBLL
                ' productAPI.DeleteProduct(productID)
                '............................................
            End If
        Next
        ' Show the Label if at least one row was deleted...
        DeleteResults.Visible = atLeastOneRowDeleted
    End Sub
    Private Sub ToggleCheckState(ByVal checkState As Boolean)
        ' Iterate through the Products.Rows property
        For Each row As GridViewRow In Products.Rows
            ' Access the CheckBox
            Dim cb As CheckBox = row.FindControl("ProductSelector")
            If cb IsNot Nothing Then
                cb.Checked = checkState
            End If
        Next
    End Sub
    Protected Sub CheckAll_Click(sender As Object, e As EventArgs) _
        Handles CheckAll.Click
        ToggleCheckState(True)
    End Sub
    Protected Sub UncheckAll_Click(sender As Object, e As EventArgs) _
        Handles UncheckAll.Click
        ToggleCheckState(False)
    End Sub
End Class

Después de copiar el marcado declarativo y el código fuente, tómese un momento para probar BatchDelete.aspx mediante un explorador. Debería ver un control GridView que enumera los diez primeros productos de GridView con cada fila que muestra el nombre, la categoría y el precio del producto junto con una casilla. Debe haber tres botones: Activar todo, Desactivar todo y Eliminar productos seleccionados. Al hacer clic en el botón Activar todo, se seleccionan todas las casillas, mientras que Desactivar todo anula la selección de todas las casillas. Al hacer clic en Eliminar productos seleccionados, se visualiza un mensaje que muestra los valores ProductID de los productos seleccionados, pero no elimina realmente los productos.

The Interface from CheckBoxField.aspx has been Moved to BatchDeleting.aspx

Figura 3: La interfaz de CheckBoxField.aspx se ha movido a BatchDeleting.aspx (haga clic para ver la imagen a tamaño completo)

Paso 2: Eliminar los productos activados mediante transacciones

Con la interfaz de eliminación por lotes copiada correctamente en BatchDeleting.aspx, todo lo que queda es actualizar el código para que el botón Eliminar productos seleccionados elimine los productos activados mediante el método DeleteProductsWithTransaction de la clase ProductsBLL. Este método, agregado en el tutorial Ajustar modificaciones de base de datos en una transacción, acepta como entrada List(Of T) de valores ProductID y elimina cada uno de los correspondientes ProductID dentro del ámbito de una transacción.

El controlador de eventos Click del botón DeleteSelectedProducts actualmente usa el siguiente bucle For Each para recorrer en iteración cada fila de GridView:

' Iterate through the Products.Rows property
For Each row As GridViewRow In Products.Rows
    ' Access the CheckBox
    Dim cb As CheckBox = row.FindControl("ProductSelector")
    If cb IsNot Nothing AndAlso cb.Checked Then
        ' Delete row! (Well, not really...)
        atLeastOneRowDeleted = True
        ' First, get the ProductID for the selected row
        Dim productID As Integer = _
            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 ...
        ' Dim productAPI As New ProductsBLL
        ' productAPI.DeleteProduct(productID)
        '............................................
    End If
Next

Para cada fila, se hace referencia mediante programación al control web CheckBox ProductSelector. Si está activada, la fila ProductID se recupera de la colección DataKeys y la propiedad Text de la etiqueta DeleteResults se actualiza para incluir un mensaje que indica que la fila se seleccionó para su eliminación.

El código anterior no elimina realmente ningún registro, ya que la llamada al método Delete de la clase ProductsBLL se comenta. Si se aplica esta lógica de eliminación, el código eliminará los productos, pero no dentro de una operación atómica. Es decir, si las primeras eliminaciones de la secuencia se realizaron correctamente, pero se produjo un error posterior (quizá debido a una infracción de restricción de clave externa), se producirá una excepción, pero esos productos ya eliminados se eliminarán.

Para garantizar la atomicidad, necesitamos usar en su lugar el método DeleteProductsWithTransaction de la clase ProductsBLL. Dado que este método acepta una lista de valores ProductID, primero necesitamos compilar esta lista desde la cuadrícula y, a continuación, pasarla como parámetro. Primero creamos una instancia de List(Of T) de tipo Integer. Dentro del bucle For Each, necesitamos agregar los valores ProductID de productos seleccionados a este List(Of T). Después del bucle, List(Of T) se debe pasar al método DeleteProductsWithTransaction de la clase ProductsBLL. Actualizamos el controlador de eventos Click del botón DeleteSelectedProducts con el siguiente código:

Protected Sub DeleteSelectedProducts_Click(sender As Object, e As EventArgs) _
    Handles DeleteSelectedProducts.Click
    
    ' Create a List to hold the ProductID values to delete
    Dim productIDsToDelete As New System.Collections.Generic.List(Of Integer)
    ' Iterate through the Products.Rows property
    For Each row As GridViewRow In Products.Rows
        ' Access the CheckBox
        Dim cb As CheckBox = CType(row.FindControl("ProductSelector"), CheckBox)
        If cb IsNot Nothing AndAlso cb.Checked Then
            ' Save the ProductID value for deletion
            ' First, get the ProductID for the selected row
            Dim productID As Integer = _
                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)
        End If
    Next
    ' Call the DeleteProductsWithTransaction method and show the Label 
    ' if at least one row was deleted...
    If productIDsToDelete.Count > 0 Then
        Dim productAPI As New ProductsBLL()
        productAPI.DeleteProductsWithTransaction(productIDsToDelete)
        DeleteResults.Visible = True
        ' Rebind the data to the GridView
        Products.DataBind()
    End If
End Sub

El código actualizado crea un List(Of T) de tipo Integer (productIDsToDelete) y lo rellena con los valores ProductID que se van a eliminar. Después del bucle For Each, si hay al menos un producto seleccionado, se llama al método DeleteProductsWithTransaction de la clase ProductsBLL y se pasa esta lista. La etiqueta DeleteResults también se muestra y los datos se vuelven a enlazar a GridView (de modo que los registros recién eliminados ya no aparezcan como filas en la cuadrícula).

En la figura 4 se muestra GridView después de seleccionar una serie de filas para su eliminación. En la figura 5 se muestra la pantalla inmediatamente después de hacer clic en el botón Eliminar productos seleccionados. Tenga en cuenta que en la figura 5, los valores ProductID de los registros eliminados se muestran en la etiqueta situada debajo de GridView y esas filas ya no están en GridView.

The Selected Products Will Be Deleted

Figura 4: Se eliminarán los productos seleccionados (haga clic para ver la imagen a tamaño completo)

The Deleted Products ProductID Values are Listed Beneath the GridView

Figura 5: Los valores de productos eliminados ProductID se enumeran debajo de GridView (haga clic para ver la imagen a tamaño completo)

Nota:

Para probar la atomicidad del método DeleteProductsWithTransaction, agregue manualmente una entrada para un producto en la tabla Order Details e intente eliminar ese producto (junto con otros). Recibirá un aviso de infracción de restricción de clave externa al intentar eliminar el producto con un pedido asociado, pero tenga en cuenta cómo se revierten las otras eliminaciones de productos seleccionados.

Resumen

La creación de una interfaz de eliminación por lotes implica agregar GridView con una columna de casillas y un control web de botón que, al hacer clic sobre él, eliminará todas las filas seleccionadas como una sola operación atómica. En este tutorial hemos creado una interfaz de este tipo mediante la unión del trabajo realizado en dos tutoriales anteriores, Agregar una columna GridView de casillas y Ajustar modificaciones de base de datos en una transacción. En el primer tutorial hemos creado una clase GridView con una columna de casillas y, en este último, hemos implementado un método en BLL que, cuando se pasa List(Of T) de valores ProductID, los elimina todos dentro del ámbito de una transacción.

En el siguiente tutorial, crearemos una interfaz para realizar inserciones por lotes.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Puede ponerse en contacto con él a través de mitchell@4GuysFromRolla.com. o a través de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Agradecimientos especiales a

Muchos revisores han evaluado esta serie de tutoriales. Los revisores principales de este tutorial fueron Hilton Giesenow y Teresa Murphy. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.