Inserción por lotes (VB)

por Scott Mitchell

Descargar PDF

Aprenda a insertar varios registros de base de datos en una sola operación. En la capa de interfaz de usuario se amplía GridView para permitir que el usuario escriba varios registros nuevos. En la capa de acceso a datos, se encapsulan las diversas operaciones de inserción dentro de una transacción para garantizar que todas las inserciones se realizan correctamente o que todas se revierten.

Introducción

En el tutorial Actualización por lotes ha visto cómo personalizar el control GridView para presentar una interfaz en la que se pueden editar varios registros. El usuario que visita la página podría realizar una serie de cambios y, después, con un solo clic de botón, realizar una actualización por lotes. En situaciones en las que los usuarios suelen actualizar muchos registros de una sola vez, una interfaz de este tipo puede guardar innumerables clics y cambios de contexto del teclado al mouse en comparación con las características predeterminadas de edición por filas individuales que se exploraron por primera vez en el tutorial Introducción a la inserción, actualización y eliminación de datos.

Este concepto también se puede aplicar al agregar registros. Imagine que en Northwind Traders normalmente se reciben envíos de proveedores que contienen una serie de productos para una categoría determinada. Por ejemplo, se podría recibir un envío de seis productos de té y café diferentes de Tokyo Traders. Si un usuario escribe los seis productos individualmente mediante un control DetailsView, tendrá que elegir muchos de los mismos valores una y otra vez: tendría que elegir la misma categoría (Beverages), el mismo proveedor (Tokyo Traders), el mismo valor sin existencias (False) y las mismas unidades en el valor de pedido (0). Esta entrada de datos repetitiva no solo consume mucho tiempo, sino que es propensa a errores.

Con un poco de trabajo se puede crear una interfaz de inserción por lotes que permita al usuario elegir el proveedor y la categoría una vez, escribir una serie de nombres de producto y precios unitarios y, después, hacer clic en un botón para agregar los nuevos productos a la base de datos (vea la figura 1). A medida que se agrega cada producto, sus campos de datos ProductName y UnitPrice se asignan los valores especificados en los controles TextBox, mientras que sus valores CategoryID y SupplierID se asignan a los valores de los controles DropDownList en la parte superior del formulario. Los valores Discontinued y UnitsOnOrder se establecen en los valores codificados de forma rígida de False y 0, respectivamente.

The Batch Inserting Interface

Figura 1: Interfaz de inserción por lotes (Haga clic para ver la imagen a tamaño completo)

En este tutorial, se creará una página que implemente la interfaz de inserción por lotes que se muestra en la figura 1. Como en los dos tutoriales anteriores, las inserciones se encapsularán dentro del ámbito de una transacción para garantizar la atomicidad. Comencemos.

Paso 1: Creación de la interfaz de presentación

Este tutorial constará de una sola página que se divide en dos regiones: una para la presentación y otra para la inserción. La interfaz de presentación, que se creará en este paso, muestra los productos en un control GridView e incluye un botón titulado Procesar envío de productos. Cuando se hace clic en este botón, la interfaz de presentación se reemplaza por la interfaz de inserción, que se muestra en la figura 1. La interfaz de presentación devuelve después de hacer clic en los botones Agregar productos desde envío o Cancelar. En el paso 2 se creará la interfaz de inserción.

Al crear una página que tiene dos interfaces, y que solo se muestra una cada vez, normalmente se colocan dentro de un control web Panel, que actúa como contenedor para otros controles. Por tanto, la página tendrá dos controles Panel, uno para cada interfaz.

Para empezar, abra la página BatchInsert.aspx en la carpeta BatchData y arrastre un control Panel desde el cuadro de herramientas al Diseñador (vea la figura 2). Establezca la propiedad ID del Panel en DisplayInterface. Al agregar el control Panel al Diseñador, sus propiedades y HeightWidth se establecen en 50px y 125px, respectivamente. Borre estos valores de propiedad de la ventana Propiedades.

Drag a Panel from the Toolbox onto the Designer

Figura 2: Arrastre de un panel desde el Cuadro de herramientas al Diseñador (Haga clic para ver la imagen a tamaño completo)

A continuación, arrastre un control Button y GridView al panel. Establezca la propiedad ID de Button en ProcessShipment y su propiedad Text en Procesar envío de producto. Establezca la propiedad ID de GridView en ProductsGrid y, desde su etiqueta inteligente, vincule a un nuevo ObjectDataSource denominado ProductsDataSource. Configure ObjectDataSource para extraer sus datos del método GetProducts de la clase ProductsBLL. Como GridView solo se usará para mostrar datos, establezca las listas desplegables de las pestañas UPDATE, INSERT y DELETE en (None). Haga clic en Finalizar para completar el Asistente para configurar orígenes de datos.

Display the Data Returned from the ProductsBLL Class s GetProducts Method

Figura 3: Presentación de los datos devueltos desde el método GetProducts de la clase ProductsBLL (Haga clic para ver la imagen a tamaño completo)

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)

Al completar el asistente de ObjectDataSource, Visual Studio agregará controles BoundField y un control CheckBoxField para cada uno de los campos de datos del producto. Quite todos los campos excepto ProductName, CategoryName, SupplierName, UnitPrice yDiscontinued. No dude en realizar cualquier personalización estética. Aquí se ha decido dar formato al campo UnitPrice como un valor de moneda, reordenar los campos y cambiar el nombre de varios de los valores HeaderText de los campos. Configure también GridView para incluir compatibilidad de paginación y ordenación; para ello, active las casillas Habilitar paginación y Habilitar ordenación en la etiqueta inteligente de GridView.

Después de agregar los controles Panel, Button, GridView y ObjectDataSource, y de personalizar los campos de GridView, el marcado declarativo de la página debe ser similar al siguiente:

<asp:Panel ID="DisplayInterface" runat="server">
    <p>
        <asp:Button ID="ProcessShipment" runat="server" 
            Text="Process Product Shipment" /> 
    </p>
    <asp:GridView ID="ProductsGrid" runat="server" AllowPaging="True" 
        AllowSorting="True" AutoGenerateColumns="False" 
        DataKeyNames="ProductID" DataSourceID="ProductsDataSource">
        <Columns>
            <asp:BoundField DataField="ProductName" HeaderText="Product" 
                SortExpression="ProductName" />
            <asp:BoundField DataField="CategoryName" HeaderText="Category" 
                ReadOnly="True" SortExpression="CategoryName" />
            <asp:BoundField DataField="SupplierName" HeaderText="Supplier" 
                ReadOnly="True" SortExpression="SupplierName" />
            <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" 
                HeaderText="Price" HtmlEncode="False" 
                SortExpression="UnitPrice">
                <ItemStyle HorizontalAlign="Right" />
            </asp:BoundField>
            <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" 
                SortExpression="Discontinued">
                <ItemStyle HorizontalAlign="Center" />
            </asp:CheckBoxField>
        </Columns>
    </asp:GridView>
    <asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
        OldValuesParameterFormatString="original_{0}"
        SelectMethod="GetProducts" TypeName="ProductsBLL">
    </asp:ObjectDataSource>
</asp:Panel>

Observe que el marcado de Button y GridView aparece dentro de las etiquetas <asp:Panel> de apertura y cierre. Como estos controles están dentro de la instancia DisplayInterface de Panel, lo puede ocultarlos si simplemente establece la propiedad Visible de Panel en False. El paso 3 se examina mediante programación el cambio de la propiedad Visible de Panel en respuesta a un clic de botón para mostrar una interfaz mientras se oculta la otra.

Ahora se verá el avance desde un explorador. Como se muestra en la figura 5, debería ver un botón Procesar envío de productos encima de un control GridView en el que se enumeran 10 productos.

The GridView Lists the Products and Offers Sorting and Paging Capabilities

Figura 5: En el control GridView se enumeran los productos y se ofrecen funcionalidades de ordenación y paginación (Haga clic para ver la imagen a tamaño completo)

Paso 2: Creación de la interfaz de inserción

Con la interfaz de presentación completa, ya puede crear la interfaz de inserción. En este tutorial, se creará una interfaz de inserción que solicite un único valor de proveedor y categoría, y después permita al usuario escribir hasta cinco nombres de producto y valores de precio unitario. Con esta interfaz, el usuario puede agregar de uno a cinco nuevos productos que comparten la misma categoría y proveedor, pero con nombres de producto y precios únicos.

Para empezar, arrastre un control Panel desde el Cuadro de herramientas al Diseñador y colóquelo debajo de la instancia DisplayInterface de Panel existente. Establezca la propiedad ID de este Panel recién agregado en InsertingInterface y su propiedad Visible en False. En el paso 3 agregará código que establece la propiedad Visible del Panel InsertingInterface en True. Borre también los valores de propiedad Height y Width del panel.

A continuación, es necesario crear la interfaz de inserción que se ha mostrado en la figura 1. Esta interfaz se puede crear mediante varias técnicas HTML, pero usará una bastante sencilla: una tabla de cuatro columnas y siete filas.

Nota:

Al escribir el marcado para los elementos <table> de HTML, es preferible usar la vista Origen. Aunque Visual Studio tiene herramientas para agregar elementos <table> desde el Diseñador, es habitual que el Diseñador inserte valores style no solicitados en el marcado. Una vez que haya creado el marcado <table>, puede volver al Diseñador para agregar los controles web y establecer sus propiedades. Al crear tablas con columnas y filas determinadas previamente, use HTML estático en lugar del control web Table, porque solo se puede acceder a los controles web colocados en un control web Table mediante el patrón FindControl("controlID"). Pero los controles web Table se pueden usar para tablas de tamaño dinámico (cuyas filas o columnas se basan en algunos criterios de base de datos o especificados por el usuario), ya que el control web Table se puede construir mediante programación.

Escriba el marcado siguiente dentro de las etiquetas <asp:Panel> de la instancia de InsertingInterface:

<table class="DataWebControlStyle" cellspacing="0">
    <tr class="BatchInsertHeaderRow">
        <td class="BatchInsertLabel">Supplier:</td>
        <td></td>
        <td class="BatchInsertLabel">Category:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertAlternatingRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertAlternatingRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertFooterRow">
        <td colspan="4">
        </td>
    </tr>
</table>

Este marcado <table> aún no incluye ningún control web, se agregarán en breve. Observe que cada elemento <tr> contiene un valor de clase CSS determinado: BatchInsertHeaderRow para la fila de encabezado donde se incluirán los controles DropDownList de proveedor y categoría; BatchInsertFooterRow para la fila de pie de página donde se incluirán los botones Agregar productos de envío y Cancelar; y valores BatchInsertRow y BatchInsertAlternatingRow alternos para las filas que contendrán los controles TextBox de producto y precio unitario. Se han creado las clases CSS correspondientes en el archivo Styles.css para dar a la interfaz de inserción una apariencia similar a la de los controles GridView y DetailsView que se han usado en estos tutoriales. Estas clases CSS se muestran a continuación.

/*** Styles for ~/BatchData/BatchInsert.aspx tutorial ***/
.BatchInsertLabel
{
    font-weight: bold;
    text-align: right;
}
.BatchInsertHeaderRow td
{
    color: White;
    background-color: #900;
    padding: 11px;
}
.BatchInsertFooterRow td
{
    text-align: center;
    padding-top: 5px;
}
.BatchInsertRow
{
}
.BatchInsertAlternatingRow
{
    background-color: #fcc;
}

Con este marcado especificado, vuelva a la vista Diseño. Esta instancia de <table> se debe mostrar como una tabla de cuatro columnas y siete filas en el Diseñador, como se muestra en la figura 6.

The Inserting Interface is Composed of a Four-Column, Seven-Row Table

Figura 6: La interfaz de inserción se compone de una tabla de cuatro columnas y siete filas (Haga clic para ver la imagen a tamaño completo)

Ya puede agregar los controles web a la interfaz de inserción. Arrastre dos controles DropDownList desde el Cuadro de herramientas a las celdas adecuadas de la tabla, uno para el proveedor y otro para la categoría.

Establezca la propiedad ID del control DropDownList de proveedor en Suppliers y enlácela a un nuevo objeto ObjectDataSource denominado SuppliersDataSource. Configure la nueva instancia de ObjectDataSource para recuperar sus datos del método GetSuppliers de la clase SuppliersBLL y establecer la lista desplegable de la pestaña UPDATE en (None). Haga clic en Finalizar para completar el asistente.

Configure the ObjectDataSource to Use the SuppliersBLL Class s GetSuppliers Method

Figura 7. Configuración de ObjectDataSource para usar el método GetSuppliers de la clase SuppliersBLL (Haga clic para ver la imagen a tamaño completo)

Haga que el control DropDownList Suppliers muestre el campo de datos CompanyName y use el campo de datos SupplierID como valores de ListItem.

Display the CompanyName Data Field and Use SupplierID as the Value

Figura 8: Representación del campo de datos CompanyName y uso de SupplierID como valor (Haga clic para ver la imagen a tamaño completo)

Asigne el nombre Categories al segundo control DropDownList y enlácelo a una nueva instancia de ObjectDataSource denominada CategoriesDataSource. Configure ObjectDataSource CategoriesDataSource para usar el método GetCategories de la clase CategoriesBLL; establezca las listas desplegables de las pestañas UPDATE y DELETE en (None), y haga clic en Finalizar para completar el asistente. Por último, haga que el control DropDownList muestre el campo de datos CategoryName y use CategoryID como valor.

Una vez agregados estos dos controles DropDownList y después de enlazarlos a elementos ObjectDataSource configurados adecuadamente, la pantalla debe ser similar a la de la figura 9.

The Header Row Now Contains the Suppliers and Categories DropDownLists

Figura 9: La fila de encabezado contiene ahora los controles DropDownList Suppliersy Categories (Haga clic para ver la imagen a tamaño completo)

Ahora es necesario crear los controles TextBox para recopilar el nombre y el precio de cada producto nuevo. Arrastre un control TextBox desde el Cuadro de herramientas al Diseñador para cada una de las cinco filas de nombre de producto y precio. Establezca las propiedades ID de los controles TextBox en ProductName1, UnitPrice1, ProductName2, UnitPrice2, ProductName3, UnitPrice3, etc.

Agregue un elemento CompareValidator después de cada uno de los cuadros de texto de precio unitario, y establezca la propiedad ControlToValidate en el valor ID adecuado. Establezca también la propiedad Operator en GreaterThanEqual, ValueToCompare en 0 y Type en Currency. Estos valores indican a CompareValidator que garantice que el precio, si se especifica, sea un valor de moneda válido mayor o igual que cero. Establezca la propiedad Text en *y ErrorMessage en El precio debe ser mayor o igual que cero. Además, omita los símbolos de moneda.

Nota:

La interfaz de inserción no incluye ningún control RequiredFieldValidator, aunque el campo ProductName de la tabla de base de datos Products no permita valores NULL. Esto se debe a que quiere permitir que el usuario escriba hasta cinco productos. Por ejemplo, si el usuario proporcionara el nombre del producto y el precio unitario en las tres primeras filas, y deja en blanco las dos últimas, simplemente se agregarían tres nuevos productos al sistema. Pero como ProductName es obligatorio, tendrá que comprobar mediante programación que si se especifica un precio unitario se proporcione un valor de nombre de producto correspondiente. Esta comprobación se abordará en el paso 4.

Al validar la entrada del usuario, CompareValidator notifica datos no válidos si el valor contiene un símbolo de moneda. Agregue un símbolo $ delante de cada uno de los controles TextBox de precio unitario como una indicación visual que indique al usuario que omita el símbolo de moneda al escribir el precio.

Por último, agregue un control ValidationSummary dentro del Panel InsertingInterface, establezca su propiedad ShowMessageBox en True y su propiedad ShowSummary en False. Con estos valores, si el usuario escribe un valor de precio unitario no válido, aparecerá un asterisco junto a los controles TextBox infractores y en ValidationSummary se mostrará un cuadro de mensaje del lado cliente con el mensaje de error que ha especificado antes.

En este momento, la pantalla debe ser similar a la de la figura 10.

The Inserting Interface Now Includes TextBoxes for the Products Names and Prices

Figura 10: La interfaz de inserción ahora incluye cuadros de texto para los nombres y precios de productos (Haga clic para ver la imagen a tamaño completo)

A continuación, es necesario agregar los botones Agregar productos desde envío y Cancelar a la fila de pie de página. Arrastre dos controles Button desde el Cuadro de herramientas al pie de página de la interfaz de inserción, y establezca las propiedades ID de los controles Button en AddProducts y CancelButton, y las propiedades Text en Agregar productos desde envío y Cancelar, respectivamente. Establezca la propiedad CausesValidation del controlCancelButton en false.

Por último, es necesario agregar un control web Label que mostrará mensajes de estado para las dos interfaces. Por ejemplo, cuando un usuario agrega correctamente un nuevo envío de productos, el objetivo es volver a la interfaz de presentación y mostrar un mensaje de confirmación. Pero si el usuario proporciona un precio para un nuevo producto, pero no el nombre del producto, es necesario mostrar un mensaje de advertencia, ya que el campo ProductName es obligatorio. Como es necesario mostrar este mensaje para ambas interfaces, colóquelo en la parte superior de la página fuera de los paneles.

Arrastre un control web Label desde el Cuadro de herramientas hasta la parte superior de la página en el Diseñador. Establezca la propiedad ID en StatusLabel, borre la propiedad Text y establezca las propiedades Visible y EnableViewState en False. Como ha visto en los tutoriales anteriores, al establecer la propiedad EnableViewState en False se puede cambiar mediante programación los valores de propiedad del control Label y hacer que vuelvan automáticamente a sus valores predeterminados en el postback siguiente. Esto simplifica el código para mostrar un mensaje de estado en respuesta a alguna acción del usuario que desaparece en el postback siguiente. Por último, establezca la propiedad CssClass del control StatusLabel en Warning, que es el nombre de una clase CSS definida en Styles.css que muestra texto en una fuente grande, en cursiva, negrita y color rojo.

En la figura 11 se muestra el Diseñador de Visual Studio después de agregar y configurar la etiqueta.

Place the StatusLabel Control Above the Two Panel Controls

Figura 11: Colocación del control StatusLabel encima de los dos controles Panel (Haga clic para ver la imagen a tamaño completo)

Paso 3: Cambio entre las interfaces de presentación e inserción

Ahora ya ha completado el marcado de la interfaces de presentación e inserción, pero todavía faltan dos tareas:

  • Cambiar entre las interfaces de presentación e inserción
  • Agregar los productos en el envío a la base de datos

Actualmente, la interfaz de presentación es visible, pero la interfaz de inserción está oculta. Esto se debe a que la propiedad Visible del control Panel DisplayInterface está establecida en True (el valor predeterminado), mientras que la propiedad Visible del control Panel InsertingInterface está establecida en False. Para cambiar entre las dos interfaces, basta con alternar el valor de propiedad Visible de cada control.

Quiere pasar de la interfaz de presentación a la interfaz de inserción cuando se hace clic en el botón Procesar envío de producto. Por tanto, cree un controlador de eventos para este evento Click de este control Button que contenga el código siguiente:

Protected Sub ProcessShipment_Click(sender As Object, e As EventArgs) _
    Handles ProcessShipment.Click
    DisplayInterface.Visible = False
    InsertingInterface.Visible = True
End Sub

Este código simplemente oculta el control Panel DisplayInterface y muestra el control Panel InsertingInterface.

A continuación, cree controladores de eventos para los controles Button Agregar productos desde envío y Cancelar en la interfaz de inserción. Cuando se hace clic en cualquiera de estos botones, es necesario volver a la interfaz de presentación. Cree controladores de eventos Click los dos controles Button a fin de que llamen a ReturnToDisplayInterface, un método que se agregará en breve. Además de ocultar el control Panel InsertingInterface y mostrar el control Panel DisplayInterface, el método ReturnToDisplayInterface debe devolver los controles web a su estado anterior a la edición. Esto implica establecer las propiedades SelectedIndex de los controles DropDownList en 0 y borrar las propiedades Text de los controles TextBox.

Nota:

Considere lo que podría ocurrir si no se devolviera a los controles a su estado anterior a la edición antes de volver a la interfaz de presentación. Un usuario podría hacer clic en el botón Procesar envío de producto, escribir los productos del envío y, después, hacer clic en Agregar productos desde el envío. Esto agregaría los productos y devolvería al usuario a la interfaz de presentación. En este momento, es posible que el usuario quiera agregar otro envío. Al hacer clic en el botón Procesar envío de producto, volvería a la interfaz de inserción, pero las selecciones de DropDownList y los valores de TextBox se rellenarían con sus valores anteriores.

Protected Sub AddProducts_Click(sender As Object, e As EventArgs) _
    Handles AddProducts.Click
    ' TODO: Save the products
    ' Revert to the display interface
    ReturnToDisplayInterface()
End Sub
Protected Sub CancelButton_Click(sender As Object, e As EventArgs) _
    Handles CancelButton.Click
    ' Revert to the display interface
    ReturnToDisplayInterface()
End Sub
Const firstControlID As Integer = 1
Const lastControlID As Integer = 5
Private Sub ReturnToDisplayInterface()
    ' Reset the control values in the inserting interface
    Suppliers.SelectedIndex = 0
    Categories.SelectedIndex = 0
    For i As Integer = firstControlID To lastControlID
        CType(InsertingInterface.FindControl _
            ("ProductName" + i.ToString()), TextBox).Text = String.Empty
        CType(InsertingInterface.FindControl _
            ("UnitPrice" + i.ToString()), TextBox).Text = String.Empty
    Next
    DisplayInterface.Visible = True
    InsertingInterface.Visible = False
End Sub

Los dos controladores de eventos Click simplemente llaman al método ReturnToDisplayInterface, aunque volverá al controlador de eventos Click de Agregar productos desde envío en el paso 4 y agregará código para guardar los productos. Para empezar, ReturnToDisplayInterface devuelve los controles DropDownList Suppliers y Categories a sus primeras opciones. Las dos constantes firstControlID y lastControlID marcan los valores de índice de control inicial y final usados en para los nombres de los controles TextBox de nombre del producto y precio unitario en la interfaz de inserción y se usan en los límites del bucle For que vuelve a establecer las propiedades Text de los controles TextBox en una cadena vacía. Por último, las propiedades Visible de los controles Panel se restablecen para ocultar la interfaz de inserción y mostrar la interfaz de presentación.

Dedique un momento a probar esta página en un explorador. Al visitar la página por primera vez, debería ver la interfaz de presentación como se muestra en la figura 5. Haga clic en el botón Procesar envío de producto. La página realizará un postback y ahora debería ver la interfaz de inserción como se muestra en la figura 12. Al hacer clic en los botones Agregar productos desde envío o Cancelar, se le devuelve a la interfaz de presentación.

Nota:

Mientras ve la interfaz de inserción, dedique un momento a probar las instancias de CompareValidator en los controles TextBox de precio unitario. Debería ver una advertencia del cuadro de mensajes del lado cliente al hacer clic en el botón Agregar productos desde envío con valores de moneda o precios no válidos con un valor inferior a cero.

The Inserting Interface is Displayed After Clicking the Process Product Shipment Button

Figura 12: La interfaz de inserción se muestra después de hacer clic en el botón Procesar envío de producto (Haga clic para ver la imagen a tamaño completo)

Paso 4: Adición de los productos

Lo que falta en este tutorial es guardar los productos en la base de datos en el controlador de eventos Click del botón Agregar productos desde envío. Esto se puede lograr si crea una instancia de ProductsDataTable y agrega una instancia de ProductsRow para cada uno de los nombres de producto proporcionados. Una vez que se agreguen estos elementos ProductsRow, realizará una llamada al método UpdateWithTransaction de la clase ProductsBLL y le pasará ProductsDataTable. Recuerde que el método UpdateWithTransaction, que se ha creado en el tutorial Encapsulación de modificaciones de base de datos dentro de una transacción, pasa ProductsDataTable al método UpdateWithTransaction de ProductsTableAdapter. Desde allí, se inicia una transacción de ADO.NET y TableAdapter emite una instrucción INSERT a la base de datos para cada elemento ProductsRow agregado en DataTable. Si todos los productos se agregan sin errores, la transacción se confirma; de lo contrario, se revierte.

El código del controlador de eventos Click del botón Agregar productos desde envío también debe realizar cierta comprobación de errores. Como no se usan instancias de RequiredFieldValidator en la interfaz de inserción, un usuario podría especificar un precio para un producto y omitir su nombre. Como el nombre del producto es obligatorio, si se produce esta condición, es necesario alertar al usuario y no continuar con las inserciones. A continuación se muestra el código completo del controlador de eventos Click:

Protected Sub AddProducts_Click(sender As Object, e As EventArgs) _
    Handles AddProducts.Click
    ' Make sure that the UnitPrice CompareValidators report valid data...
    If Not Page.IsValid Then Exit Sub
    ' Add new ProductsRows to a ProductsDataTable...
    Dim products As New Northwind.ProductsDataTable()
    For i As Integer = firstControlID To lastControlID
        ' Read in the values for the product name and unit price
        Dim productName As String = CType(InsertingInterface.FindControl _
            ("ProductName" + i.ToString()), TextBox).Text.Trim()
        Dim unitPrice As String = CType(InsertingInterface.FindControl _
            ("UnitPrice" + i.ToString()), TextBox).Text.Trim()
        ' Ensure that if unitPrice has a value, so does productName
        If unitPrice.Length > 0 AndAlso productName.Length = 0 Then
            ' Display a warning and exit this event handler
            StatusLabel.Text = "If you provide a unit price you must also 
                                include the name of the product."
            StatusLabel.Visible = True
            Exit Sub
        End If
        ' Only add the product if a product name value is provided
        If productName.Length > 0 Then
            ' Add a new ProductsRow to the ProductsDataTable
            Dim newProduct As Northwind.ProductsRow = products.NewProductsRow()
            ' Assign the values from the web page
            newProduct.ProductName = productName
            newProduct.SupplierID = Convert.ToInt32(Suppliers.SelectedValue)
            newProduct.CategoryID = Convert.ToInt32(Categories.SelectedValue)
            If unitPrice.Length > 0 Then
                newProduct.UnitPrice = Convert.ToDecimal(unitPrice)
            End If
            ' Add any "default" values
            newProduct.Discontinued = False
            newProduct.UnitsOnOrder = 0
            products.AddProductsRow(newProduct)
        End If
    Next
    ' If we reach here, see if there were any products added
    If products.Count > 0 Then
        ' Add the new products to the database using a transaction
        Dim productsAPI As New ProductsBLL()
        productsAPI.UpdateWithTransaction(products)
        ' Rebind the data to the grid so that the products just added are displayed
        ProductsGrid.DataBind()
        ' Display a confirmation (don't use the Warning CSS class, though)
        StatusLabel.CssClass = String.Empty
        StatusLabel.Text = String.Format( _
            "{0} products from supplier {1} have been " & _
            "added and filed under category {2}.", _
            products.Count, Suppliers.SelectedItem.Text, Categories.SelectedItem.Text)
        StatusLabel.Visible = True
        ' Revert to the display interface
        ReturnToDisplayInterface()
    Else
        ' No products supplied!
        StatusLabel.Text = 
            "No products were added. Please enter the " & _
            "product names and unit prices in the textboxes."
        StatusLabel.Visible = True
    End If
End Sub

Para empezar, el controlador de eventos se asegura de que la propiedad Page.IsValid devuelve un valor de True. Si devuelve False, significa que uno o varios de las instancias de CompareValidator notifican datos no válidos; en ese caso, no querrá intentar insertar los productos especificados o se iniciará una excepción al intentar asignar el valor de precio unitario especificado por el usuario a la propiedad UnitPrice de ProductsRow.

A continuación, se crea una instancia de ProductsDataTable (products). Se usa un bucle For para iterar por los controles TextBox de nombre del producto y precio unitario, y las propiedades Text se leen en las variables locales productName y unitPrice. Si el usuario ha escrito un valor para el precio unitario, pero no para el nombre de producto correspondiente, StatusLabel muestra el mensaje Si proporciona un precio unitario, también debe incluir el nombre del producto, y el controlador de eventos se cierra.

Si se ha proporcionado un nombre de producto, se crea una instancia de ProductsRow mediante el método NewProductsRow de ProductsDataTable. La propiedad ProductName de esta nueva instancia de ProductsRow se establece en el control TextBox de nombre de producto actual, mientras que las propiedades SupplierID y CategoryID se asignan a las propiedades SelectedValue de los controles DropDownList en el encabezado de la interfaz de inserción. Si el usuario ha escrito un valor para el precio del producto, se asigna a la propiedad UnitPrice de la instancia ProductsRow; de lo contrario, la propiedad se deja sin asignar, lo que dará como resultado un valor NULL para UnitPrice en la base de datos. Por último, las propiedadesDiscontinued y UnitsOnOrder se asignan a los valores codificados de forma rígida False y 0, respectivamente.

Una vez que se han asignado las propiedades a la instancia ProductsRow, se agrega a ProductsDataTable.

Al finalizar el bucle For, se comprueba si se ha agregado algún producto. Después de todo, el usuario puede haber hecho clic en Agregar productos desde envío antes de introducir los nombres o precios de los productos. Si hay al menos un producto en ProductsDataTable, se llama al método UpdateWithTransaction de la clase ProductsBLL. A continuación, los datos se vuelven a enlazar al control GridView ProductsGrid para que los productos recién agregados aparezcan en la interfaz de presentación. StatusLabel se actualiza para mostrar un mensaje de confirmación y se invoca ReturnToDisplayInterface, para ocultar la interfaz de inserción y mostrar la interfaz de presentación.

Si no se ha escrito ningún producto, la interfaz de inserción permanece visible, pero con el mensaje No se ha agregado ningún producto. Escriba los nombres de producto y los precios unitarios en los cuadros de texto.

En las figuras 13, 14 y 15 se muestran las interfaces de inserción y presentación en acción. En la figura 13, el usuario ha escrito un valor de precio unitario sin un nombre de producto correspondiente. En la figura 14 se muestra la interfaz de presentación después de que se hayan agregado correctamente tres nuevos productos, mientras que en la figura 15 se muestran dos de los productos recién agregados en el control GridView (el tercero está en la página anterior).

A Product Name is Required When Entering a Unit Price

Figura 13: Un nombre de producto es obligatorio al escribir un precio unitario (Haga clic para ver la imagen a tamaño completo)

Three New Veggies Have Been Added for the Supplier Mayumi s

Figura 14: Se han agregado tres nuevas verduras para el proveedor Mayumis (Haga clic para ver la imagen a tamaño completo)

The New Products Can Be Found in the Last Page of the GridView

Figura 15: Los nuevos productos se pueden encontrar en la última página del control GridView (Haga clic para ver la imagen a tamaño completo)

Nota:

La lógica de inserción por lotes que se usa en este tutorial encapsula las inserciones dentro del ámbito de la transacción. Para comprobarlo, introduzca intencionadamente un error de nivel de base de datos. Por ejemplo, en lugar de asignar la propiedad CategoryID de la nueva instancia ProductsRow al valor seleccionado en el control DropDownList Categories, asígnela a un valor como i * 5. Aquí, i es el indizador de bucle y tiene valores comprendidos entre 1 y 5. Por tanto, al agregar dos o más productos en la inserción por lotes, el primer producto tendrá un valor CategoryID válido (5), pero los productos siguientes tendrán valores CategoryID que no coinciden con los valores CategoryID de la tabla Categories. El efecto es que, aunque la primera operación INSERT se realizará correctamente, en las siguientes se producirá un error con una infracción de restricción de clave externa. Como la inserción por lotes es atómica, la primera operación INSERT se revertirá, y la base de datos se devolverá a su estado antes de que se iniciara el proceso de inserción por lotes.

Resumen

En este y en los dos tutoriales anteriores ha creado interfaces que permiten actualizar, eliminar e insertar lotes de datos, y en todas las operaciones se ha usado la compatibilidad con transacciones que se agregó a la capa de acceso a datos en el tutorial Encapsulación de modificaciones de base de datos dentro de una transacción. En algunos escenarios, estas interfaces de usuario de procesamiento por lotes mejoran considerablemente la eficacia del usuario final al reducir el número de clics, postbacks y cambios de contexto del teclado al mouse, al tiempo que se mantienen la integridad de los datos subyacentes.

Con este tutorial finaliza el análisis del trabajo con datos en lotes. En el siguiente conjunto de tutoriales se explora una variedad de escenarios avanzados de capa de acceso a datos, incluido el uso de procedimientos almacenados en los métodos de TableAdapter, los valores de nivel de conexión y comando de DAL, el cifrado de cadenas de conexión y mucho más.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP/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 en mitchell@4GuysFromRolla.com. o en 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 Hilton Giesenow y S ren Jacob Lauritsen. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.