Actualización por lotes (VB)

Por Scott Mitchell

Descargar PDF

Aprenda a actualizar varios registros de base de datos en una misma operación. En la capa de interfaz de usuario, creamos una clase GridView donde cada fila puede editarse. En la capa de acceso a datos, encapsulamos las diversas operaciones de actualización dentro de una transacción para garantizar que todas las actualizaciones se realizan correctamente o que todas las actualizaciones se revierten.

Introducción

En el tutorial anterior vimos cómo ampliar la capa de acceso a datos para agregar compatibilidad con las transacciones de base de datos. Las transacciones de base de datos procuran que una serie de instrucciones de modificación de datos se van a tratar como una operación atómica, lo que garantiza que o todas las modificaciones se realizan correctamente, o todas generan errores. Con esta funcionalidad de la DAL de bajo nivel fuera de nuestro camino, estamos listos para centrarnos en la creación de interfaces de modificación de datos por lotes.

En este tutorial, crearemos un control GridView donde cada fila puede editarse (vea la figura 1). Puesto que cada fila se representa en su interfaz de edición, no es necesario una columna de botones Editar, Actualizar y Cancelar. En su lugar, hay dos botones Actualizar productos en la página que, cuando se presionan, muestran las filas de GridView y actualizan la base de datos.

Each Row in the GridView is Editable

Figura 1: Cada fila de GridView es editable (haga clic para ver la imagen a tamaño completo)

¡Comencemos!

Nota:

En el tutorial Realizar actualizaciones por lotes, creamos una interfaz de edición por lotes mediante el control DataList. Este tutorial se diferencia del anterior en que usa GridView y la actualización por lotes se realiza dentro del ámbito de una transacción. Después de completar este tutorial, les animo a que vuelvan al tutorial anterior y lo actualicen para usar la funcionalidad relacionada con transacciones de base de datos agregada en el tutorial anterior.

Examinar los pasos para hacer que todas las filas de GridView sean editables

Como se describe en el tutorial Información general sobre la inserción, actualización y eliminación de datos, GridView ofrece compatibilidad integrada para editar sus datos subyacentes fila a fila. Internamente, GridView indica qué fila se puede editar a través de su propiedad EditIndex. Como GridView está enlazado a su origen de datos, comprueba cada fila para ver si el índice de la fila es igual al valor de EditIndex. Si es así, los campos de la fila se representan mediante sus interfaces de edición. En BoundField, la interfaz de edición es un TextBox cuya propiedad Text se asigna al valor del campo de datos especificado por la propiedad DataField de BoundField. En TemplateField, se usa EditItemTemplate en lugar de ItemTemplate.

Recuerde que el flujo de trabajo de edición se inicia cuando un usuario hace clic en el botón Editar de una fila. Esto provoca un postback, establece la propiedad EditIndex de GridView en el índice de la fila en la que se hace clic y vuelve a enlazar los datos a la tabla. Cuando se hace clic en el botón Cancelar de una fila, en el postback, EditIndex se establece en un valor de -1 antes de volver a enlazar los datos a la tabla. Dado que las filas de GridView comienzan a indexar en cero, establecer EditIndex en -1 tiene el efecto de mostrar GridView en modo de solo lectura.

La propiedad EditIndex funciona bien en la edición fila a fila, pero no está diseñada para la edición por lotes. Para que todo el control GridView sea editable, es necesario que cada fila se represente mediante su interfaz de edición. La manera más fácil de lograrlo es crear, donde cada campo editable se implementa, como un control TemplateField con su interfaz de edición definida en ItemTemplate.

En los pasos siguientes, crearemos un control GridView completamente editable. En el paso 1, empezaremos creando el control GridView y su ObjectDataSource y convirtiendo sus elementos BoundField y CheckBoxField en TemplateField. En los pasos 2 y 3 moveremos las interfaces de edición de los EditItemTemplate de TemplateField a sus ItemTemplate correspondientes.

Paso 1: Mostrar información del producto

Antes de preocuparnos por la creación de una clase GridView en la que se puedan editar filas, comencemos simplemente mostrando la información del producto. Abra la página BatchUpdate.aspx en la carpeta BatchData y arrastre un control GridView desde el cuadro de herramientas al Diseñador. Establezca el ID de GridView en ProductsGrid y, desde su etiqueta inteligente, enlácelo a un nuevo objeto ObjectDataSource denominado ProductsDataSource. Configure ObjectDataSource para recuperar sus datos del método GetProducts de la clase ProductsBLL.

Configure the ObjectDataSource to Use the ProductsBLL Class

Figura 2: Configuración de ObjectDataSource para usar la clase ProductsBLL (haga clic para ver la imagen a tamaño completo)

Retrieve the Product Data Using the GetProducts Method

Figura 3: Recuperación de la información del producto usando el método GetProducts (haga clic aquí para ver la imagen a tamaño completo)

Al igual que GridView, las características de modificación de ObjectDataSource están diseñadas para funcionar fila a fila. Para actualizar un conjunto de registros, será necesario escribir código en la clase de código subyacente de la página ASP.NET que agrupe los datos por lotes y los pase a la BLL. Por lo tanto, establezca las listas desplegables de las pestañas ACTUALIZAR, INSERTAR y ELIMINAR de ObjectDataSource en (Ninguno). Haga clic en Finalizar para completar el asistente.

Set the Drop-Down Lists in the UPDATE, INSERT, and DELETE Tabs to (None)

Figura 4: Establecimiento de las listas desplegables de las pestañas ACTUALIZAR, INSERTAR y ELIMINAR en (Ninguno) (haga clic para ver la imagen a tamaño completo)

Después de completar el asistente para la configuración de orígenes de datos, el marcado declarativo de ObjectDataSource debe ser similar al siguiente:

<asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProducts" TypeName="ProductsBLL">
</asp:ObjectDataSource>

Completar el asistente para la configuración de orígenes de datos también hace que Visual Studio cree controles BoundField y un control CheckBoxField para los campos de datos del producto en GridView. En este tutorial, vamos a dejar que el usuario solo pueda ver y editar el nombre del producto, la categoría, el precio y el estado interrumpido. Quite todos los campos ProductName, CategoryName, UnitPrice y Discontinued cambie el nombre de las propiedades HeaderText de los tres primeros campos a Product, Category y Price respectivamente. Por último, active las casillas Habilitar paginación y Habilitar ordenación en la etiqueta inteligente de GridView.

En este punto, GridView tiene tres elementos BoundField (ProductName, CategoryName y UnitPrice) y un elemento CheckBoxField (Discontinued). Es necesario convertir estos cuatro campos en TemplateField y, a continuación, mover la interfaz de edición desde el elemento EditItemTemplate de TemplateField a su ItemTemplate correspondiente.

Nota:

Ya vimos la creación y personalización de controles TemplateField en el tutorial Personalizar la interfaz de modificación de datos. Le guiaremos por los pasos para convertir BoundField y CheckBoxField en TemplateField y definir sus interfaces de edición en sus ItemTemplate correspondientes, pero si ve que se atasca o que necesita repasar algo, no dude en volver a revisar este tutorial anterior.

En la etiqueta inteligente de GridView, haga clic en el vínculo Editar columnas para abrir el cuadro de diálogo Campos. A continuación, seleccione cada campo y haga clic en el vínculo Convertir este campo en un vínculo de TemplateField.

Convert the Existing BoundFields and CheckBoxField Into TemplateFields

Figura 5: Conversión de los elementos BoundField y CheckBoxField existentes en TemplateField

Ahora que cada campo es un TemplateField, ya estamos listos para mover la interfaz de edición de EditItemTemplate a ItemTemplate.

Paso 2: Crear las interfaces de edición de ProductName, UnitPrice y Discontinued

La creación de las interfaces de edición de ProductName, UnitPrice y Discontinued es el tema de este paso y es algo bastante sencillo, ya que cada interfaz ya está definida en el elemento EditItemTemplate de TemplateField. La creación de la interfaz de edición de CategoryName es un poco más laboriosa, ya que es necesario crear un objeto DropDownList de las categorías aplicables. Abordaremos esta interfaz de edición de CategoryName en el paso 3.

Comencemos con el elemento TemplateField ProductName. Haga clic en el vínculo Editar plantillas de la etiqueta inteligente de GridView y explore en profundidad hasta llegar a EditItemTemplate del TemplateField ProductName. Seleccione un objeto TextBox, cópielo en el Portapapeles y péguelo en ItemTemplate del TemplateField ProductName. Establezca la propiedad ID del TextBox en ProductName.

Ahora, agregue RequiredFieldValidator a ItemTemplate para asegurarse de que el usuario proporciona un valor a cada nombre del producto. Establezca la propiedad ControlToValidate en ProductName, la propiedad ErrorMessage en Debe proporcionar el nombre del producto y la propiedad Text en *. Después de realizar estas adiciones a ItemTemplate, su pantalla debe parecerse a la de la figura 6.

The ProductName TemplateField Now Includes a TextBox and a RequiredFieldValidator

Figura 6: Ahora, el TemplateField ProductName incluye un TextBox y un RequiredFieldValidator (haga clic para ver la imagen a tamaño completo)

En cuanto a la interfaz de edición de UnitPrice, empiece por copiar el TextBox de EditItemTemplate a ItemTemplate. A continuación, coloque un $ delante del TextBox y establezca su propiedad ID en UnitPrice y su propiedad Columns, en 8.

Agregue también un objeto CompareValidator al elemento ItemTemplate de UnitPrice para asegurarse de que el valor especificado por el usuario sea un valor de moneda válido mayor o igual que 0,00 USD. Establezca la propiedad ControlToValidate de CompareValidator en UnitPrice, la propiedad ErrorMessage en Debe especificar un valor de moneda válido. Omita cualquier símbolo de moneda, la propiedad Text en *, la propiedad Type en Currency, la propiedad Operator en GreaterThanEqual y la propiedad ValueToCompare en 0.

Add a CompareValidator to Ensure the Price Entered is a Non-Negative Currency Value

Figura 7: Adición de CompareValidator para garantizar que el precio especificado es un valor de moneda no negativo (haga clic para ver la imagen a tamaño completo)

Respecto al TemplateField Discontinued, puede usar el objeto CheckBox ya definido en ItemTemplate. Simplemente, establezca ID en Interrumpido y su propiedad Enabled, en True.

Paso 3: Crear la interfaz de edición de CategoryName

La interfaz de edición del elemento EditItemTemplate del TemplateField CategoryName contiene un objeto TextBox que muestra el valor del campo de datos CategoryName. Es necesario reemplazarlo por un objeto DropDownList que muestre las posibles categorías.

Nota:

El tutorial Personalización de la interfaz de modificación de datos contiene una explicación más exhaustiva y completa sobre cómo personalizar una plantilla para incluir DropDownList en lugar de TextBox. Aunque aquí los pasos están completos, se describen de forma muy concisa. Para obtener una visión más detallada sobre la creación y configuración de las categorías de DropDownList, consulte el tutorial Personalización de la interfaz de modificación de datos.

Arrastre un DropDownList desde el cuadro de herramientas hasta el elemento ItemTemplate del TemplateField CategoryName, estableciendo ID en Categories. En este punto, normalmente definiríamos el origen de datos del DropDownList a través de su etiqueta inteligente, lo que crearía un nuevo ObjectDataSource. Sin embargo, esto agregaría ObjectDataSource a ItemTemplate, lo que crearía una instancia de ObjectDataSource para cada fila de GridView. En su lugar, vamos a crear ObjectDataSource fuera del TemplateField de GridView. Termine de editar la plantilla y arrastre un ObjectDataSource desde el cuadro de herramientas al Diseñador, debajo del ObjectDataSource ProductsDataSource. Denomine el nuevo ObjectDataSource CategoriesDataSource y configúrelo para que use el método GetCategories de la clase CategoriesBLL.

Configure the ObjectDataSource to Use the CategoriesBLL Class

Figura 8: Configuración de ObjectDataSource para usar la clase CategoriesBLL (haga clic para ver la imagen a tamaño completo)

Retrieve the Category Data Using the GetCategories Method

Figura 9: Recuperación de la información de categoría usando el método GetCategories (haga clic aquí para ver la imagen a tamaño completo)

Puesto que este ObjectDataSource se usa simplemente para recuperar datos, establezca las listas desplegables de las pestañas ACTUALIZAR y ELIMINAR en (Ninguno). Haga clic en Finalizar para completar el asistente.

Set the Drop-Down Lists in the UPDATE and DELETE Tabs to (None)

Figura 10: Establecimiento de las listas desplegables de las pestañas ACTUALIZAR y ELIMINAR en (Ninguno) (haga clic para ver la imagen a tamaño completo)

Tras completar el asistente, el marcado declarativo de CategoriesDataSource debe tener un aspecto similar al siguiente:

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>

Con CategoriesDataSource creado y configurado, vuelva al elemento ItemTemplate del TemplateField CategoryName y, en la etiqueta inteligente DropDownList, haga clic en el vínculo Elegir origen de datos. En el asistente para la configuración de orígenes de datos, seleccione la opción CategoriesDataSource de la primera lista desplegable y elija usar CategoryName para la presentación y CategoryID como valor.

Bind the DropDownList to the CategoriesDataSource

Figura 11: Enlace de DropDownList a CategoriesDataSource (haga clic para ver la imagen a tamaño completo)

En este momento, DropDownList Categories muestra todas las categorías, pero aún no selecciona automáticamente la categoría adecuada para el producto enlazado a la fila de GridView. Para ello, es necesario establecer el elemento SelectedValue del DropDownList Categories en el valor CategoryID del producto. Haga clic en el vínculo Editar enlaces de datos de la etiqueta inteligente de DropDownList y asocie la propiedad SelectedValue con el campo de datos CategoryID, como se muestra en la figura 12.

Bind the Product s CategoryID Value to the DropDownList s SelectedValue Property

Figura 12: Enlace del valor CategoryID del producto a la propiedad SelectedValue de DropDownList

Sigue habiendo un último problema: si el producto no tiene un valor CategoryID especificado, la instrucción de enlace de datos en SelectedValue producirá una excepción. Esto se debe a que DropDownList contiene solo elementos para las categorías, y no ofrece una opción para aquellos productos que tienen un valor de base de datos NULL para CategoryID. Para solucionarlo, establezca la propiedad AppendDataBoundItems de DropDownList en True y agregue un nuevo elemento a DropDownList, omitiendo la propiedad Value de la sintaxis declarativa. Es decir, asegúrese de que la sintaxis declarativa del DropDownList Categories es similar a la siguiente:

<asp:DropDownList ID="Categories" runat="server" AppendDataBoundItems="True" 
    DataSourceID="CategoriesDataSource" DataTextField="CategoryName" 
    DataValueField="CategoryID" SelectedValue='<%# Bind("CategoryID") %>'>
    <asp:ListItem Value=">-- Select One --</asp:ListItem>
</asp:DropDownList>

Fíjese en que <asp:ListItem Value=""> -- Select One -- tiene el atributo Value establecido explícitamente en una cadena vacía. Consulte el tutorial Personalización de la interfaz de modificación de datos para obtener una explicación más detallada sobre por qué se necesita este elemento DropDownList adicional para controlar el caso NULL y por qué la asignación de la propiedad Value a una cadena vacía es esencial.

Nota:

Se puede producir un posible problema de rendimiento y escalabilidad aquí que vale la pena mencionar. Puesto que cada fila tiene un DropDownList que usa CategoriesDataSource como origen de datos, se llamará al método GetCategories de la clase CategoriesBLLn veces por cada visita de página, donde n es el número de filas de GridView. Estas n llamadas a GetCategories dan lugar a n consultas a la base de datos. Este impacto en la base de datos podría reducirse almacenando en caché las categorías devueltas, ya sea en una memoria caché por solicitud o a través de la capa de almacenamiento en caché, mediante una dependencia de almacenamiento en caché de SQL o estableciendo un periodo de expiración muy breve.

Paso 4: Completar la interfaz de edición

Hemos realizado una serie de cambios en las plantillas de GridView sin pausa para ver nuestros avances. Vamos a dedicar un momento a ver nuestros avances en un explorador. Como se muestra en la figura 13, cada fila se representa mediante su ItemTemplate, que contiene la interfaz de edición de la celda.

Each GridView Row is Editable

Figura 13: Cada fila de GridView es editable (haga clic para ver la imagen a tamaño completo)

Hay algunos problemas de formato de índole menor que debemos tener en cuenta en este momento. En primer lugar, fíjese en que el valor de UnitPrice contiene cuatro cifras decimales. Para corregirlo, vuelva al elemento ItemTemplate del TemplateField UnitPrice y, en la etiqueta inteligente del TextBox, haga clic en el vínculo Editar enlaces de datos. A continuación, especifique que la propiedad Text debe tener un formato numérico.

Format the Text Property as a Number

Figura 14: Formato de la propiedad Text como un número

En segundo lugar, centremos la casilla de la columna Discontinued (en lugar de tenerla alineada a la izquierda). Haga clic en Editar columnas en la etiqueta inteligente de GridView y seleccione el TemplateField Discontinued en la lista de campos de la esquina inferior izquierda. Explore ItemStyle en profundidad y establezca la propiedad HorizontalAlign en Centrar como se muestra en la figura 15.

Center the Discontinued CheckBox

Figura 15: Centrado del CheckBox Discontinued

A continuación, agregue un control ValidationSummary a la página y establezca su propiedad ShowMessageBox en True y su propiedad ShowSummary en False. Agregue también los controles web Button que, al presionarse, van a actualizar los cambios del usuario. En concreto, agregue dos controles web Button: uno encima de GridView y otro debajo, estableciendo las propiedades Text de ambos controles en Actualizar productos.

Dado que la interfaz de edición de GridView se define en los elementos ItemTemplate de sus TemplateField, los elementos EditItemTemplate son superfluos y se pueden eliminar.

Tras realizar los cambios de formato mencionados anteriormente, agregar los controles Button y quitar los elementos EditItemTemplate innecesarios, la sintaxis declarativa de la página debe ser similar a la siguiente:

<p>
    <asp:Button ID="UpdateAllProducts1" runat="server" Text="Update Products" />
</p>
<p>
    <asp:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False" 
        DataKeyNames="ProductID" DataSourceID="ProductsDataSource" 
        AllowPaging="True" AllowSorting="True">
        <Columns>
            <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
                <ItemTemplate>
                    <asp:TextBox ID="ProductName" runat="server" 
                        Text='<%# Bind("ProductName") %>'></asp:TextBox>
                    <asp:RequiredFieldValidator ID="RequiredFieldValidator1" 
                        ControlToValidate="ProductName"
                        ErrorMessage="You must provide the product's name." 
                        runat="server">*</asp:RequiredFieldValidator>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Category" 
                SortExpression="CategoryName">
                <ItemTemplate>
                    <asp:DropDownList ID="Categories" runat="server" 
                        AppendDataBoundItems="True" 
                        DataSourceID="CategoriesDataSource"
                        DataTextField="CategoryName" 
                        DataValueField="CategoryID" 
                        SelectedValue='<%# Bind("CategoryID") %>'>
                        <asp:ListItem>-- Select One --</asp:ListItem>
                    </asp:DropDownList>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Price" 
                SortExpression="UnitPrice">
                <ItemTemplate>
                    $<asp:TextBox ID="UnitPrice" runat="server" Columns="8" 
                        Text='<%# Bind("UnitPrice", "{0:N}") %>'></asp:TextBox>
                    <asp:CompareValidator ID="CompareValidator1" runat="server" 
                        ControlToValidate="UnitPrice"
                        ErrorMessage="You must enter a valid currency value. 
                                      Please omit any currency symbols."
                        Operator="GreaterThanEqual" Type="Currency" 
                        ValueToCompare="0">*</asp:CompareValidator>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Discontinued" SortExpression="Discontinued">
                <ItemTemplate>
                    <asp:CheckBox ID="Discontinued" runat="server" 
                        Checked='<%# Bind("Discontinued") %>' />
                </ItemTemplate>
                <ItemStyle HorizontalAlign="Center" />
            </asp:TemplateField>
        </Columns>
    </asp:GridView>
</p>
<p>
    <asp:Button ID="UpdateAllProducts2" runat="server" Text="Update Products" />
    <asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
        OldValuesParameterFormatString="original_{0}"
        SelectMethod="GetProducts" TypeName="ProductsBLL">
    </asp:ObjectDataSource>
    <asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
        OldValuesParameterFormatString="original_{0}"
        SelectMethod="GetCategories" TypeName="CategoriesBLL">
    </asp:ObjectDataSource>
    <asp:ValidationSummary ID="ValidationSummary1" runat="server" 
        ShowMessageBox="True" ShowSummary="False" />
</p>

En la figura 16 se muestra esta página vista a través de un explorador después de agregar los controles web Button y los cambios de formato realizados.

The Page Now Includes Two Update Products Buttons

Figura 16: Ahora, la página incluye dos botones Actualizar productos (haga clic para ver la imagen a tamaño completo)

Paso 5: Actualizar los productos

Cuando un usuario visite esta página, realizará sus modificaciones y, a continuación, hará clic en uno de los dos botones Actualizar productos. Llegado ese punto, es necesario guardar de alguna manera los valores especificados por el usuario en cada fila en una instancia de ProductsDataTable y, a continuación, pasarlo a un método de la BLL que luego pasará esa instancia de ProductsDataTable al método UpdateWithTransaction de la DAL. El método UpdateWithTransaction, que creamos en el tutorial anterior, garantiza que el lote de cambios se actualizará como una operación atómica.

Cree un método llamado BatchUpdate en BatchUpdate.aspx.vb y agregue el siguiente código:

Private Sub BatchUpdate()
    ' Enumerate the GridView's Rows collection and create a ProductRow
    Dim productsAPI As New ProductsBLL()
    Dim products As Northwind.ProductsDataTable = productsAPI.GetProducts()
    For Each gvRow As GridViewRow In ProductsGrid.Rows
        ' Find the ProductsRow instance in products that maps to gvRow
        Dim productID As Integer = _
            Convert.ToInt32(ProductsGrid.DataKeys(gvRow.RowIndex).Value)
        Dim product As Northwind.ProductsRow = products.FindByProductID(productID)
        If product IsNot Nothing Then
            ' Programmatically access the form field elements in the 
            ' current GridViewRow
            Dim productName As TextBox = _
                CType(gvRow.FindControl("ProductName"), TextBox)
            Dim categories As DropDownList = _
                CType(gvRow.FindControl("Categories"), DropDownList)
            Dim unitPrice As TextBox = _
                CType(gvRow.FindControl("UnitPrice"), TextBox)
            Dim discontinued As CheckBox = _
                CType(gvRow.FindControl("Discontinued"), CheckBox)
            ' Assign the user-entered values to the current ProductRow
            product.ProductName = productName.Text.Trim()
            If categories.SelectedIndex = 0 Then 
                product.SetCategoryIDNull() 
            Else 
                product.CategoryID = Convert.ToInt32(categories.SelectedValue)
            End If
            If unitPrice.Text.Trim().Length = 0 Then 
                product.SetUnitPriceNull() 
            Else 
                product.UnitPrice = Convert.ToDecimal(unitPrice.Text)
            End If
            product.Discontinued = discontinued.Checked
        End If
    Next
    ' Now have the BLL update the products data using a transaction
    productsAPI.UpdateWithTransaction(products)
End Sub

Este método comienza colocando todos los productos de nuevo en una ProductsDataTable a través de una llamada al método GetProducts de la BLL. A continuación, muestra la colección Rows del GridView ProductGrid. La colección Rows contiene una instancia de GridViewRow de cada fila que se muestra en GridView. Como estamos mostrando un máximo de diez filas por página, la colección Rows del GridView no tendrá más de diez elementos.

Por cada fila, se obtiene el ProductID de la colección DataKeys y se selecciona la ProductsRow correspondiente en ProductsDataTable. Se hace referencia mediante programación a los cuatro controles de entrada de TemplateField y sus valores se asignan a las propiedades de la instancia de ProductsRow. Después de haber usado los valores de cada fila de GridView para actualizar ProductsDataTable, se pasa al método UpdateWithTransaction de la BLL que, como vimos en el tutorial anterior, simplemente llama al método UpdateWithTransaction de la DAL.

El algoritmo de actualización por lotes usado en este tutorial actualiza cada fila en ProductsDataTable que se corresponda con una fila de GridView, independientemente de si la información de producto ha cambiado. 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. Volviendo al tutorial Realizar actualizaciones por lotes, allí exploramos una interfaz de actualización por lotes con DataList y agregamos código que actualizaría solo los registros que el usuario modificó realmente. No dude en usar las técnicas de Realizar actualizaciones por lotes para actualizar el código de este tutorial, si lo desea.

Nota:

Al enlazar el origen de datos a GridView a través de su etiqueta inteligente, Visual Studio asigna automáticamente los valores de clave principal del origen de datos a la propiedad DataKeyNames de GridView. Si no enlazó ObjectDataSource a GridView a través de la etiqueta inteligente de GridView como se describe en el paso 1, deberá establecer manualmente la propiedad DataKeyNames de GridView en ProductID para tener acceso al valor de ProductID de cada fila a través de la colección DataKeys.

El código usado en BatchUpdate es similar al usado en los métodos UpdateProduct de la BLL; la principal diferencia es que en los métodos UpdateProduct solo se recupera una única instancia de ProductRow de la arquitectura. El código que asigna las propiedades de ProductRow es el mismo entre los métodos UpdateProducts y el código dentro del bucle For Each en BatchUpdate, como establece el patrón general.

Para completar este tutorial, es necesario invocar el método BatchUpdate cuando se hace clic en alguno de los dos botones Actualizar productos. Cree controladores de eventos para los eventos Click de estos dos controles Button y agregue el siguiente código a los controladores de eventos:

BatchUpdate()
ClientScript.RegisterStartupScript(Me.GetType(), "message", _
    "alert('The products have been updated.');", True)

En primer lugar, se realiza una llamada a BatchUpdate. A continuación, se usa la propiedad ClientScript para insertar JavaScript que mostrará un cuadro de mensaje que dice Los productos se han actualizado.

Pruebe este código un momento. Visite BatchUpdate.aspx a través de un explorador, edite una serie de filas y haga clic en uno de los botones Actualizar productos. Suponiendo que no haya errores de validación de entrada, debería aparecer un cuadro de mensaje que diga Los productos se han actualizado. Para confirmar que la actualización es atómica, considere la posibilidad de agregar una restricción CHECK aleatoria, como una que no permita valores de UnitPrice de 1234,56. A continuación, en BatchUpdate.aspx, edite una serie de registros, procurando establecer uno de los valores de UnitPrice del producto en el valor prohibido (1234,56). Esto debería generar un error al hacer clic en Actualizar productos y hacer que los demás cambios durante esa operación por lotes se reviertan a sus valores originales.

Un método BatchUpdate alternativo

El método BatchUpdate que acabamos de ver recupera todos los productos del método GetProducts de la BLL y acto seguido actualiza solo los registros que aparecen en GridView. Este enfoque es ideal si GridView no usa la paginación, pero si lo hace, puede haber cientos, miles o decenas de miles de productos, pero solo diez filas en GridView. En tal caso, obtener todos los productos de la base de datos solo para modificar 10 de ellos no es lo ideal.

En estas situaciones, considere la posibilidad de usar el siguiente método BatchUpdateAlternate en su lugar:

Private Sub BatchUpdateAlternate()
    ' Enumerate the GridView's Rows collection and create a ProductRow
    Dim productsAPI As New ProductsBLL()
    Dim products As New Northwind.ProductsDataTable()
    For Each gvRow As GridViewRow In ProductsGrid.Rows
        ' Create a new ProductRow instance
        Dim productID As Integer = _
            Convert.ToInt32(ProductsGrid.DataKeys(gvRow.RowIndex).Value)
        Dim currentProductDataTable As Northwind.ProductsDataTable = _
            productsAPI.GetProductByProductID(productID)
        If currentProductDataTable.Rows.Count > 0 Then
            Dim product As Northwind.ProductsRow = currentProductDataTable(0)
            Dim productName As TextBox = _
                CType(gvRow.FindControl("ProductName"), TextBox)
            Dim categories As DropDownList = _
                CType(gvRow.FindControl("Categories"), DropDownList)
            Dim unitPrice As TextBox = _
                CType(gvRow.FindControl("UnitPrice"), TextBox)
            Dim discontinued As CheckBox = _
                CType(gvRow.FindControl("Discontinued"), CheckBox)
            ' Assign the user-entered values to the current ProductRow
            product.ProductName = productName.Text.Trim()
            If categories.SelectedIndex = 0 Then 
                product.SetCategoryIDNull() 
            Else 
                product.CategoryID = Convert.ToInt32(categories.SelectedValue)
            End If
            If unitPrice.Text.Trim().Length = 0 Then 
                product.SetUnitPriceNull() 
            Else 
                product.UnitPrice = Convert.ToDecimal(unitPrice.Text)
            End If
            product.Discontinued = discontinued.Checked
            ' Import the ProductRow into the products DataTable
            products.ImportRow(product)
        End If
    Next
    ' Now have the BLL update the products data using a transaction
    productsAPI.UpdateProductsWithTransaction(products)
End Sub

BatchMethodAlternate comienza creando un nuevo ProductsDataTable vacío denominado products. A continuación, recorre la colección Rows de GridView y, por cada fila, obtiene la información de producto concreta mediante el método GetProductByProductID(productID) de la BLL. Las propiedades de la instancia de ProductsRow recuperada se actualizan de la misma manera que BatchUpdate, pero después de actualizar la fila, dicha instancia se importa a productsProductsDataTable través del método ImportRow(DataRow) de DataTable.

Una vez completado el bucle For Each, products contiene una instancia de ProductsRow por cada fila de GridView. Como cada una de las instancias de ProductsRow se ha agregado a products (en lugar de actualizarse), si la pasamos ciegamente al método UpdateWithTransaction, ProductsTableAdapter intentará insertar cada uno de los registros en la base de datos. En su lugar, es necesario especificar que cada una de estas filas se ha modificado (y no agregado).

Esto se puede lograr agregando un nuevo método a la BLL denominado UpdateProductsWithTransaction. UpdateProductsWithTransaction, mostrado abajo, establece el elemento RowState de cada una de las instancias de ProductsRow de ProductsDataTable en Modified y, a continuación, pasa ProductsDataTable al método UpdateWithTransaction de la DAL.

Public Function UpdateProductsWithTransaction _
    (ByVal products As Northwind.ProductsDataTable) As Integer
    ' Mark each product as Modified
    products.AcceptChanges()
    For Each product As Northwind.ProductsRow In products
        product.SetModified()
    Next
    ' Update the data via a transaction
    Return UpdateWithTransaction(products)
End Function

Resumen

GridView proporciona capacidades integradas de edición fila a fila, pero no permite crear interfaces totalmente editables. Como hemos visto en este tutorial, estas interfaces son factibles, pero requieren un poco de trabajo. Para crear un elemento GridView en el que se pueda editar cada fila, es necesario convertir los campos de GridView en TemplateField y definir la interfaz de edición dentro de cada ItemTemplate. Además, se deben agregar controles web Button del tipo Actualizar todo a la página, separados de GridView. Los controladores de eventos Click de estos objetos Button deben mostrar la colección Rows de GridView, almacenar los cambios en ProductsDataTable y pasar la información actualizada al método de la BLL adecuado.

En el siguiente tutorial veremos cómo crear una interfaz para eliminar por lotes. En concreto, cada fila GridView incluirá una casilla y, en lugar botones del tipo Actualizar todos, aparecerán botones Eliminar filas seleccionadas.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, lleva 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 han sido Teresa Murphy y David Suru. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.