Actualización por lotes (C#)

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, se creará un control GridView donde cada fila se puede editar. En la capa de acceso a datos, se encapsulan 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 ha visto 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 garantizan que una serie de instrucciones de modificación de datos se traten 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 DAL de bajo nivel fuera del camino, se puede centrar en la creación de interfaces de modificación de datos por lotes.

En este tutorial, creará un control GridView donde cada fila se puede editar (vea la figura 1). Como 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 Realización de actualizaciones por lotes ha creado una interfaz de edición por lotes mediante el control DataList. Este tutorial se diferencia del anterior en que usa un control GridView y la actualización por lotes se realiza dentro del ámbito de una transacción. Después de completar este tutorial, le animo a que vuelva al tutorial anterior y lo actualice para usar la funcionalidad relacionada con las transacciones de base de datos agregada en el tutorial anterior.

Examen de 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 mediante 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 los controles BoundField, la interfaz de edición es un control TextBox cuya propiedad Text se asigna al valor del campo de datos especificado por la propiedad DataField del control BoundField. En los controles 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. Como las filas de GridView se empiezan a indexar en cero, establecer EditIndex en -1 tiene el efecto de mostrar el control 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, creará un control GridView completamente editable. En el paso 1, primero creará el control GridView y su ObjectDataSource, y convertirá sus controles BoundField y CheckBoxField en controles TemplateField. En los pasos 2 y 3 moverá las interfaces de edición de los valores EditItemTemplate de TemplateField a sus valores ItemTemplate correspondientes.

Paso 1: Representación de información del producto

Antes de preocuparse por la creación de un control GridView en el que se puedan editar filas, comience 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 valorID de GridView en ProductsGrid y, desde su etiqueta inteligente, elija enlazarlo a una nueva instancia de ObjectDataSource denominada 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 con el método GetProducts (Haga clic 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 BLL. Por tanto, establezca las listas desplegables de las pestañas UPDATE, INSERT y DELETE de ObjectDataSource en (None). 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 UPDATE, INSERT y DELETE en (None) (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, se dejará que el usuario solo pueda ver y editar el nombre del producto, la categoría, el precio y el estado descatalogado. Quite todos los campos menos ProductName, CategoryName, UnitPrice y Discontinued, y 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, el control GridView tiene tres elementos BoundField (ProductName, CategoryName y UnitPrice) y un elemento CheckBoxField (Discontinued). Es necesario convertir estos cuatro campos en controles TemplateField y, después, mover la interfaz de edición desde el elemento EditItemTemplate de TemplateField a su instancia de ItemTemplate correspondiente.

Nota:

Ya ha visto la creación y personalización de controles TemplateField en el tutorial Personalización de la interfaz de modificación de datos. Se le guiarás por los pasos para convertir los controles BoundField y CheckBoxField en controles TemplateField y definir sus interfaces de edición en sus elementos ItemTemplate correspondientes, pero si se atasca o necesita repasar algo, no dude en volver a revisar este tutorial anterior.

En la etiqueta inteligente del control 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 TemplateField

Figura 5: Conversión de los controles BoundField y CheckBoxField existentes en un control TemplateField

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

Paso 2: Creación de 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, porque 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 control DropDownList de las categorías aplicables. Esta interfaz de edición de CategoryName se abordará en el paso 3.

Comenzará 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 de TemplateField ProductName. Seleccione el control TextBox, cópielo en el Portapapeles y péguelo en ItemTemplate del control TemplateField ProductName. Establezca la propiedad ID del control 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, la pantalla debe parecerse a la de la figura 6.

The ProductName TemplateField Now Includes a TextBox and a RequiredFieldValidator

Figura 6: Ahora, el control TemplateField ProductName incluye un control TextBox y un control 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 control TextBox de EditItemTemplate a ItemTemplate. A continuación, coloque un símbolo $ delante del control 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 del validador 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 control TemplateField Discontinued, puede usar el control CheckBox ya definido en ItemTemplate. Simplemente, establezca ID en Descatalogado y su propiedad Enabled en true.

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

La interfaz de edición del elemento EditItemTemplate del control TemplateField CategoryName contiene un control TextBox que muestra el valor del campo de datos CategoryName. Es necesario reemplazarlo por un control 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 un control DropDownList en lugar de un control 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 control DropDownList desde el Cuadro de herramientas hasta el elemento ItemTemplate del control TemplateField CategoryName, y establezca ID en Categories. En este punto, normalmente definiría el origen de datos del control DropDownList desde su etiqueta inteligente, lo que crearía una instancia de ObjectDataSource. Pero esto agregaría ObjectDataSource a ItemTemplate, lo que crearía una instancia de ObjectDataSource para cada fila de GridView. En su lugar, se creará ObjectDataSource fuera de los objetos TemplateField de GridView. Termine de editar la plantilla y arrastre una instancia de ObjectDataSource desde el Cuadro de herramientas al Diseñador, debajo de la instancia de ObjectDataSource ProductsDataSource. Asigne el nombre CategoriesDataSource al nuevo objeto ObjectDataSource 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 con el método GetCategories (Haga clic para ver la imagen a tamaño completo)

Como esta instancia de ObjectDataSource se usa simplemente para recuperar datos, establezca las listas desplegables de las pestañas UPDATE y DELETE en (None). 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 UPDATE y DELETE en (None) (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 de TemplateField CategoryName y, en la etiqueta inteligente de 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, en el control DropDownList Categories se muestran 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 control 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 control 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. Como cada fila tiene un control 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 si se almacenan en caché las categorías devueltas, ya sea en una caché por solicitud o desde la capa de almacenamiento en caché, mediante una dependencia de almacenamiento en caché de SQL o si se establece un periodo de expiración muy breve.

Paso 4: Finalización de la interfaz de edición

Ha realizado una serie de cambios en las plantillas de GridView sin pausa para ver los avances. Tómese un momento para ver el progreso en un explorador. Como se muestra en la figura 13, cada fila se representa mediante su valor 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 se deben 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 control TemplateField UnitPrice y, en la etiqueta inteligente del control 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, se centra 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 control 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 control 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, y establezca las propiedades Text de ambos controles en Actualizar productos.

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

Después de 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 en un explorador después de agregar los controles web Button y realizar los cambios de formato.

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: Actualización de los productos

Cuando un usuario visite esta página, realizará sus modificaciones y, después, 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, después, 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 se ha creado 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.cs y agregue el siguiente código:

private void BatchUpdate()
{
    // Enumerate the GridView's Rows collection and create a ProductRow
    ProductsBLL productsAPI = new ProductsBLL();
    Northwind.ProductsDataTable products = productsAPI.GetProducts();
    foreach (GridViewRow gvRow in ProductsGrid.Rows)
    {
        // Find the ProductsRow instance in products that maps to gvRow
        int productID = Convert.ToInt32(ProductsGrid.DataKeys[gvRow.RowIndex].Value);
        Northwind.ProductsRow product = products.FindByProductID(productID);
        if (product != null)
        {
            // Programmatically access the form field elements in the 
            // current GridViewRow
            TextBox productName = (TextBox)gvRow.FindControl("ProductName");
            DropDownList categories = 
                (DropDownList)gvRow.FindControl("Categories");
            TextBox unitPrice = (TextBox)gvRow.FindControl("UnitPrice");
            CheckBox discontinued = 
                (CheckBox)gvRow.FindControl("Discontinued");
            // Assign the user-entered values to the current ProductRow
            product.ProductName = productName.Text.Trim();
            if (categories.SelectedIndex == 0) 
                product.SetCategoryIDNull(); 
            else 
                product.CategoryID = Convert.ToInt32(categories.SelectedValue);
            if (unitPrice.Text.Trim().Length == 0) 
                product.SetUnitPriceNull(); 
            else 
                product.UnitPrice = Convert.ToDecimal(unitPrice.Text);
            product.Discontinued = discontinued.Checked;
        }
    }
    // Now have the BLL update the products data using a transaction
    productsAPI.UpdateWithTransaction(products);
}

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

Por cada fila, se obtiene el valor ProductID de la colección DataKeys y se selecciona el valor ProductsRow correspondiente de 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 ha visto 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 del producto ha cambiado. 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. Volviendo al tutorial Realización de actualizaciones por lotes, allí se exploró una interfaz de actualización por lotes con DataList y se agregó código para actualizar solo los registros que el usuario haya modificado realmente. No dude en usar las técnicas de Realización de actualizaciones por lotes para actualizar el código de este tutorial, si quiere.

Nota:

Al enlazar el origen de datos al control GridView desde 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 ha enlazado ObjectDataSource a GridView desde la etiqueta inteligente de GridView como se describe en el paso 1, deberá establecer manualmente la propiedad DataKeyNames de GridView en ProductID para acceder al valor de ProductID de cada fila desde 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 foreach 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(this.GetType(), "message", 
    "alert('The products have been updated.');", true);

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

Dedique un minuto a probar este código. Visite BatchUpdate.aspx desde un explorador, edite una serie de filas y haga clic en uno de los botones Actualizar productos. Si no hay 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 se permitan valores UnitPrice de 1234,56. Después, en BatchUpdate.aspx, edite una serie de registros, procurando establecer uno de los valores 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 acaba de ver recupera todos los productos del método GetProducts de la BLL y después actualiza solo los registros que aparecen en el control GridView. Este enfoque es ideal si el control 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 ese 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 void BatchUpdateAlternate()
{
    // Enumerate the GridView's Rows collection and create a ProductRow
    ProductsBLL productsAPI = new ProductsBLL();
    Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
    foreach (GridViewRow gvRow in ProductsGrid.Rows)
    {
        // Create a new ProductRow instance
        int productID = Convert.ToInt32(ProductsGrid.DataKeys[gvRow.RowIndex].Value);
        
        Northwind.ProductsDataTable currentProductDataTable = 
            productsAPI.GetProductByProductID(productID);
        if (currentProductDataTable.Rows.Count > 0)
        {
            Northwind.ProductsRow product = currentProductDataTable[0];
            // Programmatically access the form field elements in the 
            // current GridViewRow
            TextBox productName = (TextBox)gvRow.FindControl("ProductName");
            DropDownList categories = 
                (DropDownList)gvRow.FindControl("Categories");
            TextBox unitPrice = (TextBox)gvRow.FindControl("UnitPrice");
            CheckBox discontinued = 
                (CheckBox)gvRow.FindControl("Discontinued");
            // Assign the user-entered values to the current ProductRow
            product.ProductName = productName.Text.Trim();
            if (categories.SelectedIndex == 0) 
                product.SetCategoryIDNull(); 
            else 
                product.CategoryID = Convert.ToInt32(categories.SelectedValue);
            if (unitPrice.Text.Trim().Length == 0) 
                product.SetUnitPriceNull(); 
            else 
                product.UnitPrice = Convert.ToDecimal(unitPrice.Text);
            product.Discontinued = discontinued.Checked;
            // Import the ProductRow into the products DataTable
            products.ImportRow(product);
        }
    }
    // Now have the BLL update the products data using a transaction
    productsAPI.UpdateProductsWithTransaction(products);
}

BatchMethodAlternate comienza creando una instancia de ProductsDataTable vacía con el nombre products. Después, 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, se importa a products``ProductsDataTable mediante el método ImportRow(DataRow) de DataTable.

Una vez que se complete el bucle foreach, 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 se pasa a ciegas 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 si se agrega un nuevo método a la BLL denominado UpdateProductsWithTransaction. UpdateProductsWithTransaction, como se muestra a continuación, establece el elemento RowState de cada una de las instancias de ProductsRow de ProductsDataTable en Modified y, después, pasa ProductsDataTable al método UpdateWithTransaction de la DAL.

public int UpdateProductsWithTransaction(Northwind.ProductsDataTable products)
{
    // Mark each product as Modified
    products.AcceptChanges();
    foreach (Northwind.ProductsRow product in products)
        product.SetModified();
    // Update the data via a transaction
    return UpdateWithTransaction(products);
}

Resumen

El control GridView proporciona funciones integradas de edición fila a fila, pero no permite crear interfaces totalmente editables. Como ha visto en este tutorial, estas interfaces son factibles, pero requieren un poco de trabajo. Para crear un control GridView en el que se pueda editar cada fila, es necesario convertir los campos de GridView en instancias de TemplateField y definir la interfaz de edición dentro de cada elemento ItemTemplate. Además, se deben agregar controles web Button del tipo Actualizar todo a la página, independientes del control 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 verá cómo crear una interfaz para la eliminación por lotes. En concreto, cada fila de GridView incluirá una casilla y, en lugar botones de tipo Actualizar todos, aparecerán botones Eliminar filas seleccionadas.

¡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 Teresa Murphy y David Suru. ¿Le interesaría revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.