Eliminación por lotes (C#)
por Scott Mitchell
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 modificable. 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.
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.
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.cs
dentro de la clase de código subyacente en BatchDelete.aspx.cs
(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:
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);
}
}
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.
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<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 foreach
para recorrer en iteración cada fila de 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 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<T>
de tipo int
. Dentro del bucle foreach
, necesitamos agregar los valores ProductID
de productos seleccionados a este List<T>
. Después del bucle, List<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 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();
}
}
El código actualizado crea un List<T>
de tipo int
(productIDsToDelete
) y lo rellena con los valores ProductID
que se van a eliminar. Después del bucle foreach
, 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.
Figura 4: Se eliminarán los productos seleccionados (Haga clic para ver la imagen a tamaño completo)
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<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 de su blog, que se puede encontrar en http://ScottOnWriting.NET.
Agradecimientos especiales a
Esta serie de tutoriales contó con la revisión de muchos revisores que fueron de gran ayuda. Los revisores principales de este tutorial fueron Hilton Giesenow y Teresa Murphy. ¿Le interesaría revisar mis próximos artículos de MSDN? Si fuera así, escríbame a mitchell@4GuysFromRolla.com.