Realizar actualizaciones por lotes (VB)

por Scott Mitchell

Descargar PDF

Obtenga información sobre cómo crear un objeto 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 elemento DataList de nivel de elemento. Al igual que en 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 elemento 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 objeto DataList totalmente editable se puede modificar (hacer clic para ver la imagen a tamaño completo).

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

Paso 1: Crear la interfaz de usuario editable en ItemTemplate de DataList

En el tutorial anterior, donde creamos un objeto DataList estándar de nivel de elemento editable, hemos 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é 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 objeto DataList totalmente editable.

Para un objeto DataList totalmente editable, queremos 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, TextBoxes para los valores de dirección, ciudad y país o región.

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

Create a New ObjectDataSource Named SuppliersDataSource

Figura 2: creación de un nuevo elemento ObjectDataSource denominado 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, trabajaremos directamente con la capa de lógica de negocios. Por lo tanto, establezca la lista desplegable en (Ninguno) 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 (Ninguno) 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 TextBoxes 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:

Al igual que con el tutorial anterior, DataList de este tutorial debe tener habilitado su estado de visualización.

En ItemTemplate, estoy usando 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 de 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 TextBoxes 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: Agregar 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 elemento TextBox, actualmente no hay ningún botón Actualizar disponible. En lugar de tener un botón Actualizar por elemento, con objetos 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, vamos a agregar 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, vamos a refactorizar esa lógica en un tercer método, UpdateAllSupplierAddresses, teniendo los controladores de eventos al implemente invocar este tercer método.

Protected Sub UpdateAll1_Click(sender As Object, e As EventArgs) _
    Handles UpdateAll1.Click
    UpdateAllSupplierAddresses()
End Sub
Protected Sub UpdateAll2_Click(sender As Object, e As EventArgs) _
    Handles UpdateAll2.Click
    UpdateAllSupplierAddresses()
End Sub
Private Sub UpdateAllSupplierAddresses()
    ' TODO: Write code to update _all_ of the supplier addresses in the DataList
End Sub

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: Actualizar 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 DataList mediante la propiedad Items de DataList. Con una referencia a DataListItem, podemos obtener el correspondiente SupplierID 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 Sub UpdateAllSupplierAddresses()
    ' Create an instance of the SuppliersBLL class
    Dim suppliersAPI As New SuppliersBLL()
    ' Iterate through the DataList's items
    For Each item As DataListItem In Suppliers.Items
        ' Get the supplierID from the DataKeys collection
        Dim supplierID As Integer = Convert.ToInt32(Suppliers.DataKeys(item.ItemIndex))
        ' Read in the user-entered values
        Dim address As TextBox = CType(item.FindControl("Address"), TextBox)
        Dim city As TextBox = CType(item.FindControl("City"), TextBox)
        Dim country As TextBox = CType(item.FindControl("Country"), TextBox)
        Dim addressValue As String = Nothing, _
            cityValue As String = Nothing, _
            countryValue As String = Nothing
        If address.Text.Trim().Length > 0 Then
            addressValue = address.Text.Trim()
        End If
        If city.Text.Trim().Length > 0 Then
            cityValue = city.Text.Trim()
        End If
        If country.Text.Trim().Length > 0 Then
            countryValue = country.Text.Trim()
        End If
        ' Call the SuppliersBLL class's UpdateSupplierAddress method
        suppliersAPI.UpdateSupplierAddress _
            (supplierID, addressValue, cityValue, countryValue)
    Next
End Sub

Cuando el usuario hace clic en uno de los botones Actualizar todo, el método UpdateAllSupplierAddresses recorre en iteración cada DataListItem en el objeto DataList Suppliers y llama al método UpdateSupplierAddress de la clase SuppliersBLL, pasando los valores correspondientes. Un valor no especificado para los pasos de dirección, ciudad o país o región es un valor de Nothing a UpdateSupplierAddress (en lugar de una cadena en blanco), lo que da como resultado una base de datos NULL 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 proporciona 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 de 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 está auditando 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 actualizamos la información de dirección del proveedor especificado leyendo primero el registro de proveedor único en un elemento SuppliersDataTable y, después, establecemos los valores de las columnas Address, City y Country mediante el código siguiente:

Public Function UpdateSupplierAddress _
    (supplierID As Integer, address As String, city As String, country As String) _
    As Boolean
    Dim suppliers As Northwind.SuppliersDataTable = _
        Adapter.GetSupplierBySupplierID(supplierID)
    If suppliers.Count = 0 Then
        ' no matching record found, return false
        Return False
    Else
        Dim supplier As Northwind.SuppliersRow = suppliers(0)
        If address Is Nothing Then
            supplier.SetAddressNull()
        Else
            supplier.Address = address
        End If
        If city Is Nothing Then
            supplier.SetCityNull()
        Else
            supplier.City = city
        End If
        If country Is Nothing Then
            supplier.SetCountryNull()
        Else
            supplier.Country = country
        End If
        ' Update the supplier Address-related information
        Dim rowsAffected As Integer = Adapter.Update(supplier)
        ' Return true if precisely one row was updated, otherwise false
        Return rowsAffected = 1
    End If
End Function

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 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 hemos 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 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 ciegamente los 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 Is Nothing AndAlso Not supplier.IsAddressNull() Then
    supplier.SetAddressNull()
ElseIf (address IsNot Nothing AndAlso supplier.IsAddressNull) _
    OrElse (Not supplier.IsAddressNull() AndAlso _
                String.Compare(supplier.Address, address) <> 0) Then
    supplier.Address = address
End If
If city Is Nothing AndAlso Not supplier.IsCityNull() Then
    supplier.SetCityNull()
ElseIf (city IsNot Nothing AndAlso supplier.IsCityNull) _
    OrElse (Not supplier.IsCityNull() AndAlso _
                String.Compare(supplier.City, city) <> 0) Then
    supplier.City = city
End If
If country Is Nothing AndAlso Not supplier.IsCountryNull() Then
    supplier.SetCountryNull()
ElseIf (country IsNot Nothing AndAlso supplier.IsCountryNull) _
    OrElse (Not supplier.IsCountryNull() AndAlso _
                String.Compare(supplier.Country, country) <> 0) Then
    supplier.Country = country
End If

Con este código agregado, el método Update de 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íamos realizar un 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 omita la llamada al método Update de DAL. Este enfoque funciona bien si usa el método directo de base de datos, ya que el método directo de base de datos 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 creando 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 podrían actualizar los registros en los que se han producido cambios.

Resumen

En este tutorial, hemos visto cómo crear un objeto DataList totalmente editable, lo que permite a un usuario modificar rápidamente la información de dirección de varios proveedores. Hemos 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, hemos 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/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 via 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 han sido Zack Jones y Ken Pespisa. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.