Realizar actualizaciones por lotes (C#)

por Scott Mitchell

Descargar PDF

Obtenga información sobre cómo crear un control DataList totalmente editable donde todos sus elementos están en modo de edición y cuyos valores se pueden guardar haciendo clic en un botón "Actualizar todo" en la página.

Introducción

En el tutorial anterior se ha examinado cómo crear un control DataList de nivel de elemento. Al igual que en el control GridView editable estándar, cada elemento de DataList incluía un botón Editar que, cuando se hacía clic, convertía el elemento en editable. Aunque esta edición de nivel de elemento funciona bien para los datos que solo se actualizan ocasionalmente, algunos escenarios de casos de uso requieren que el usuario edite muchos registros. Si un usuario necesita editar docenas de registros y se ve obligado a hacer clic en Editar, realizar sus cambios y hacer clic en Actualizar en cada uno, la cantidad de clics puede dificultar su productividad. En tales situaciones, una mejor opción es proporcionar un control DataList totalmente editable, donde todos sus elementos están en modo de edición y cuyos valores se pueden editar haciendo clic en un botón Actualizar todo en la página (vea la figura 1).

Each Item in a Fully Editable DataList can be Modified

Figura 1: Cada elemento de un control DataList totalmente editable se puede modificar (Haga clic para ver la imagen a tamaño completo)

En este tutorial, se examinará cómo permitir que los usuarios actualicen la información de direcciones de los proveedores mediante un control DataList totalmente editable.

Paso 1: Creación de la interfaz de usuario editable en ItemTemplate de DataList

En el tutorial anterior, en el que se creó un control DataList estándar de nivel de elemento editable, se han usado dos plantillas:

  • ItemTemplate contenía la interfaz de usuario de solo lectura (los controles web Label para mostrar el nombre y precio de cada producto).
  • EditItemTemplate contenía la interfaz de usuario del modo de edición (los dos controles web TextBox).

La propiedad EditItemIndex de DataList determina qué elemento DataListItem se representa (si existe) mediante EditItemTemplate. En concreto, el elemento DataListItem cuyo valor ItemIndex coincide con la propiedad EditItemIndex de DataList se representa mediante EditItemTemplate. Este modelo funciona bien cuando solo se puede editar un elemento a la vez, pero hace aguas al crear un control DataList totalmente editable.

Para un control DataList totalmente editable, quiere que todos los elementos DataListItem se representen mediante la interfaz editable. La manera más sencilla de lograrlo es definir la interfaz editable en ItemTemplate. A fin de modificar la información de dirección de los proveedores, la interfaz editable contiene el nombre del proveedor como texto y, después, controles TextBox para los valores de dirección, ciudad y país o región.

Para empezar, abra la página BatchUpdate.aspx, agregue un control DataList y establezca su propiedad ID en Suppliers. En la etiqueta inteligente de DataList, opte por agregar un nuevo control ObjectDataSource denominado SuppliersDataSource.

Create a New ObjectDataSource Named SuppliersDataSource

Figura 2: Creación de una instancia de ObjectDataSource con el nombre SuppliersDataSource (Haga clic para ver la imagen a tamaño completo)

Configure ObjectDataSource para recuperar datos mediante el método GetSuppliers() de la clase SuppliersBLL (vea la figura 3). Al igual que con el tutorial anterior, en lugar de actualizar la información del proveedor mediante ObjectDataSource, se trabajará directamente con la capa de lógica de negocios. Por tanto, establezca la lista desplegable en (None) en la pestaña UPDATE (vea la figura 4).

Retrieve Supplier Information Using the GetSuppliers() Method

Figura 3: Recuperación de información de proveedor mediante el método GetSuppliers() ( Haga clic para ver la imagen a tamaño completo)

Set the Drop-Down List to (None) in the UPDATE Tab

Figura 4: Establecimiento de la lista desplegable en (None) en la pestaña UPDATE (Haga clic para ver la imagen a tamaño completo)

Después de completar el asistente, Visual Studio genera automáticamente el elemento ItemTemplate de DataList para mostrar cada campo de datos que devuelve el origen de datos en un control web Label. Es necesario modificar esta plantilla para que proporcione la interfaz de edición en su lugar. ItemTemplate se puede personalizar mediante el Diseñador con la opción Editar plantillas de la etiqueta inteligente de DataList o directamente mediante la sintaxis declarativa.

Dedique un momento a crear una interfaz de edición en la que se muestre el nombre del proveedor como texto, pero incluya controles TextBox para los valores de dirección, ciudad y país o región del proveedor. Después de realizar estos cambios, la sintaxis declarativa de la página debe ser similar al siguiente:

<asp:DataList ID="Suppliers" runat="server" DataKeyField="SupplierID"
    DataSourceID="SuppliersDataSource">
    <ItemTemplate>
        <h4><asp:Label ID="CompanyNameLabel" runat="server"
            Text='<%# Eval("CompanyName") %>' /></h4>
        <table border="0">
            <tr>
                <td class="SupplierPropertyLabel">Address:</td>
                <td class="SupplierPropertyValue">
                    <asp:TextBox ID="Address" runat="server"
                        Text='<%# Eval("Address") %>' />
                </td>
            </tr>
            <tr>
                <td class="SupplierPropertyLabel">City:</td>
                <td class="SupplierPropertyValue">
                    <asp:TextBox ID="City" runat="server"
                        Text='<%# Eval("City") %>' />
                </td>
            </tr>
            <tr>
                <td class="SupplierPropertyLabel">Country:</td>
                <td class="SupplierPropertyValue">
                    <asp:TextBox ID="Country" runat="server"
                        Text='<%# Eval("Country") %>' />
                </td>
            </tr>
        </table>
        <br />
    </ItemTemplate>
</asp:DataList>
<asp:ObjectDataSource ID="SuppliersDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetSuppliers" TypeName="SuppliersBLL">
</asp:ObjectDataSource>

Nota:

Como en el tutorial anterior, DataList de este tutorial debe tener habilitado su estado de visualización.

En ItemTemplate, se usan dos nuevas clases CSS, SupplierPropertyLabel y SupplierPropertyValue, que se han agregado a la clase Styles.css y configurado para usar los mismos valores de estilo que las clases CSS ProductPropertyLabel y ProductPropertyValue.

.ProductPropertyLabel, .SupplierPropertyLabel
{
    font-weight: bold;
    text-align: right;
}
.ProductPropertyValue, .SupplierPropertyValue
{
    padding-right: 35px;
}

Después de realizar estos cambios, visite esta página mediante un explorador. Como se muestra en la figura 5, cada elemento de DataList muestra el nombre del proveedor como texto y usa controles TextBox para mostrar la dirección, la ciudad y el país o región.

Each Supplier in the DataList is Editable

Figura 5: Cada proveedor de DataList es editable (Haga clic para ver la imagen a tamaño completo)

Paso 2: Adición de un botón Actualizar todo

Aunque cada proveedor de la figura 5 tiene sus campos de dirección, ciudad y país o región mostrados en un control TextBox, actualmente no hay ningún botón Actualizar disponible. En lugar de tener un botón Actualizar por elemento, con controles DataList totalmente editables, normalmente hay un solo botón Actualizar todo en la página que, al hacer clic, actualiza todos los registros de DataList. En este tutorial, se agregarán dos botones Actualizar todo: uno en la parte superior de la página y otro en la parte inferior (aunque hacer clic en cualquiera de los botones tendrá el mismo efecto).

Empiece por agregar un control web Button encima de DataList y establezca su propiedad ID en UpdateAll1. A continuación, agregue el segundo control web Button debajo de DataList, estableciendo su propiedad ID en UpdateAll2. Establezca las propiedades Text de los dos controles Button en Actualizar todo. Por último, cree controladores de eventos para ambos eventos Click de Button. En lugar de duplicar la lógica de actualización en cada uno de los controladores de eventos, se refactorizará esa lógica en un tercer método, UpdateAllSupplierAddresses, y los controladores de eventos invocarán este tercer método.

protected void UpdateAll1_Click(object sender, EventArgs e)
{
    UpdateAllSupplierAddresses();
}
protected void UpdateAll2_Click(object sender, EventArgs e)
{
    UpdateAllSupplierAddresses();
}
private void UpdateAllSupplierAddresses()
{
    // TODO: Write code to update _all_ of the supplier addresses in the DataList
}

En la figura 6 se muestra la página después de agregar los botones Actualizar todo.

Two Update All Buttons have been Added to the Page

Figura 6: Se han agregado dos botones Actualizar todo a la página (Haga clic para ver la imagen a tamaño completo)

Paso 3: Actualización de toda la información de direcciones de proveedores

Con todos los elementos de DataList que muestran la interfaz de edición y con la incorporación de los botones Actualizar todo, lo que queda es escribir el código para realizar la actualización por lotes. En concreto, es necesario recorrer en bucle los elementos de DataList y llamar al método UpdateSupplierAddress de la clase SuppliersBLL para cada uno.

Se puede acceder a la colección de instancias de DataListItem que conforman el control DataList mediante la propiedad Items de DataList. Con una referencia a DataListItem, se puede obtener el valor SupplierID correspondiente de la colección DataKeys y hacer referencia mediante programación a los controles web TextBox dentro de ItemTemplate, como se muestra en el código siguiente:

private void UpdateAllSupplierAddresses()
{
    // Create an instance of the SuppliersBLL class
    SuppliersBLL suppliersAPI = new SuppliersBLL();
    // Iterate through the DataList's items
    foreach (DataListItem item in Suppliers.Items)
    {
        // Get the supplierID from the DataKeys collection
        int supplierID = Convert.ToInt32(Suppliers.DataKeys[item.ItemIndex]);
        // Read in the user-entered values
        TextBox address = (TextBox)item.FindControl("Address");
        TextBox city = (TextBox)item.FindControl("City");
        TextBox country = (TextBox)item.FindControl("Country");
        string addressValue = null, cityValue = null, countryValue = null;
        if (address.Text.Trim().Length > 0)
            addressValue = address.Text.Trim();
        if (city.Text.Trim().Length > 0)
              cityValue = city.Text.Trim();
        if (country.Text.Trim().Length > 0)
            countryValue = country.Text.Trim();
        // Call the SuppliersBLL class's UpdateSupplierAddress method
        suppliersAPI.UpdateSupplierAddress
            (supplierID, addressValue, cityValue, countryValue);
    }
}

Cuando el usuario hace clic en uno de los botones Actualizar todo, el método UpdateAllSupplierAddresses itera por todos los valores DataListItem en el control DataList Suppliers y llama al método UpdateSupplierAddress de la clase SuppliersBLL, pasando los valores correspondientes. Un valor no especificado para la dirección, ciudad o país o región para un valor de Nothing a UpdateSupplierAddress (en lugar de una cadena en blanco), lo que da como resultado un valor NULL de base de datos para los campos del registro subyacente.

Nota:

Como mejora, es posible que quiera agregar un control web Label de estado a la página que proporcione algún mensaje de confirmación después de realizar la actualización por lotes.

Actualización solo de las direcciones que se han modificado

El algoritmo de actualización por lotes usado para este tutorial llama al método UpdateSupplierAddress para cada proveedor del control DataList, independientemente de si se ha cambiado su información de dirección. Aunque estas actualizaciones ciegas no suelen generar un problema de rendimiento, pueden provocar registros superfluos si se auditan los cambios en la tabla de base de datos. Por ejemplo, si usa desencadenadores a fin de registrar todos los elementos UPDATE de la tabla Suppliers en una tabla de auditoría, cada vez que un usuario hace clic en el botón Actualizar todo, se creará un registro de auditoría para cada proveedor del sistema, independientemente de si el usuario ha realizado algún cambio.

Las clases DataTable y DataAdapter de ADO.NET están diseñadas para admitir actualizaciones por lotes en las que solo se modifican, eliminan y se producen nuevos registros en cualquier comunicación de base de datos. Cada fila de DataTable tiene una propiedad RowState que indica si la fila se ha agregado a DataTable, se ha eliminado, se ha modificado o bien si permanece sin cambios. Cuando se rellena inicialmente un objeto DataTable, todas las filas se marcan sin cambios. Cambiar el valor de cualquiera de las columnas de la fila marca la fila como modificada.

En la clase SuppliersBLL se actualiza la información de dirección del proveedor especificado; para ello, primero se lee el registro de proveedor único en un elemento SuppliersDataTable y, después, se establecen los valores de las columnas Address, City y Country mediante el código siguiente:

public bool UpdateSupplierAddress
    (int supplierID, string address, string city, string country)
{
    Northwind.SuppliersDataTable suppliers =
        Adapter.GetSupplierBySupplierID(supplierID);
    if (suppliers.Count == 0)
        // no matching record found, return false
        return false;
    else
    {
        Northwind.SuppliersRow supplier = suppliers[0];
        if (address == null)
            supplier.SetAddressNull();
        else
            supplier.Address = address;
        if (city == null)
            supplier.SetCityNull();
        else
            supplier.City = city;
        if (country == null)
            supplier.SetCountryNull();
        else
            supplier.Country = country;
        // Update the supplier Address-related information
        int rowsAffected = Adapter.Update(supplier);
        // Return true if precisely one row was updated,
        // otherwise false
        return rowsAffected == 1;
    }
}

Este código asigna de forma ingenua los valores de dirección, ciudad y país o región pasados a SuppliersRow en SuppliersDataTable, independientemente de si los valores han cambiado o no. Estas modificaciones hacen que la propiedad RowState de SuppliersRow se marque como modificada. Cuando se llama al método Update de la capa de acceso a datos, detecta que SupplierRow se ha modificado y, por tanto, envía un comando UPDATE a la base de datos.

Pero imagine que ha agregado código a este método para asignar solo los valores de dirección, ciudad y país o región pasados si difieren de los valores existentes de SuppliersRow. En el caso de que la dirección, la ciudad y el país o región sean los mismos que los datos existentes, no se realizará ningún cambio y los elementos RowState de SupplierRow se dejarán marcados como sin cambios. El resultado neto es que cuando se llama al método Update de la DAL, no se realizará ninguna llamada a la base de datos porque SuppliersRow no se ha modificado.

Para aplicar este cambio, reemplace las instrucciones que asignan a ciegaslos valores de dirección, ciudad y país o región pasados con el código siguiente:

// Only assign the values to the SupplierRow's column values if they differ
if (address == null && !supplier.IsAddressNull())
    supplier.SetAddressNull();
else if ((address != null && supplier.IsAddressNull()) ||
         (!supplier.IsAddressNull() &&
         string.Compare(supplier.Address, address) != 0))
    supplier.Address = address;
if (city == null && !supplier.IsCityNull())
    supplier.SetCityNull();
else if ((city != null && supplier.IsCityNull()) ||
         (!supplier.IsCityNull() && string.Compare(supplier.City, city) != 0))
    supplier.City = city;
if (country == null && !supplier.IsCountryNull())
    supplier.SetCountryNull();
else if ((country != null && supplier.IsCountryNull()) ||
         (!supplier.IsCountryNull() &&
         string.Compare(supplier.Country, country) != 0))
    supplier.Country = country;

Con este código agregado, el método Update de la DAL envía una instrucción UPDATE a la base de datos solo para aquellos registros cuyos valores relacionados con la dirección han cambiado.

Como alternativa, podría realizar el seguimiento de si hay diferencias entre los campos de dirección pasados y los datos de la base de datos y, si no hay ninguno, simplemente omitir la llamada al método Update de la DAL. Este enfoque funciona bien si usa el método directo de base de datos, ya que en ese método no se pasa a una instancia de SuppliersRow cuya instancia de RowState se puede comprobar para determinar si realmente se necesita una llamada de base de datos.

Nota:

Cada vez que se invoca el método UpdateSupplierAddress, se realiza una llamada a la base de datos para recuperar información sobre el registro actualizado. Después, si hay algún cambio en los datos, se realiza otra llamada a la base de datos para actualizar la fila de la tabla. Este flujo de trabajo podría optimizarse con la creación de una sobrecarga del método UpdateSupplierAddress que acepte una instancia de EmployeesDataTable que tenga todos los cambios de la página BatchUpdate.aspx. Después, podría realizar una llamada a la base de datos para obtener todos los registros de la tabla Suppliers. Luego, los dos conjuntos de resultados se podrían enumerar y solo se actualizarían los registros en los que se han producido cambios.

Resumen

En este tutorial, ha visto cómo crear un control DataList totalmente editable, lo que permite a un usuario modificar rápidamente la información de dirección de varios proveedores. Ha empezado por definir la interfaz de edición de un control web TextBox para los valores de dirección, ciudad y región del proveedor en el elemento ItemTemplate de DataList. A continuación, se han agregado los botones Actualizar todo encima y debajo de DataList. Una vez que un usuario ha realizado sus cambios y ha hecho clic en uno de los botones Actualizar todo, se enumeran los elementos DataListItem y se realiza una llamada al método UpdateSupplierAddress de la clase SuppliersBLL.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP y ASP.NET, y fundador de 4GuysFromRolla.com, trabaja 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 fue revisada por muchos revisores. Los revisores principales de este tutorial han sido Zack Jones y Ken Pespisa. ¿Le interesaría revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.