Realizar actualizaciones por lotes (VB)
por Scott Mitchell
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).
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
.
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).
Figura 3: Recuperación de información de proveedor mediante el método GetSuppliers()
( Haga clic para ver la imagen a tamaño completo)
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.
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 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.
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 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
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 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 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 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 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/ASP.NET y fundador de 4GuysFromRolla.com, ha trabajado 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.