Actualizar TableAdapter para usar JOIN (C#)

por Scott Mitchell

Descargar PDF

Al trabajar con una base de datos, es habitual solicitar datos que están distribuidos entre varias tablas. Para recuperar datos de dos tablas diferentes, podemos usar una subconsulta correlacionada o una operación JOIN. En este tutorial se comparan las subconsultas correlacionadas y la sintaxis de JOIN antes de examinar cómo crear un TableAdapter que incluya una operación JOIN en su consulta principal.

Introducción

Con las bases de datos relacionales, los datos con los que estamos interesados en trabajar a menudo están distribuidos entre varias tablas. Por ejemplo, al mostrar información del producto, es probable que deseemos enumerar los nombres de categoría y proveedor correspondientes a cada producto. La tabla Products tiene los valores CategoryID y SupplierID, pero los nombres reales de categoría y proveedor se encuentran en las tablas Categories y Suppliers, respectivamente.

Para recuperar información de otra tabla relacionada, podemos usar subconsultas correlacionadas u operaciones JOIN. Una subconsulta correlacionada es una consulta SELECT anidada que hace referencia a columnas de la consulta externa. Por ejemplo, en el tutorial Creación de una capa de acceso a datos, usamos dos subconsultas correlacionadas en la consulta principal de ProductsTableAdapter para devolver los nombres de categoría y proveedor de cada producto. Una JOIN es una construcción SQL que combina filas relacionadas de dos tablas diferentes. Hemos utilizado una JOIN en el tutorial Consulta de datos con el control SqlDataSource para mostrar información de categorías con cada producto.

La razón por la que nos hemos abstenido de usar operaciones JOIN con TableAdapters se debe a las limitaciones del asistente de TableAdapter para generar automáticamente las instrucciones INSERT, UPDATE y DELETE correspondientes. Más concretamente, si la consulta principal de TableAdapter contiene operaciones JOIN, TableAdapter no puede crear automáticamente las instrucciones SQL ad hoc ni los procedimientos almacenados para sus propiedades InsertCommand, UpdateCommand y DeleteCommand.

En este tutorial, compararemos y contrastaremos brevemente las subconsultas correlacionadas y las JOIN antes de explorar cómo crear un TableAdapter que incluya operaciones JOIN en su consulta principal.

Comparar y contrastar subconsultas correlacionadas y JOIN

Recuerde que el ProductsTableAdapter creado en el primer tutorial del conjunto de datos Northwind usa subconsultas correlacionadas para devolver los nombres de categoría y proveedor correspondientes de cada producto. A continuación, se muestra la consulta principal de ProductsTableAdapter.

SELECT ProductID, ProductName, SupplierID, CategoryID, 
       QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, 
       ReorderLevel, Discontinued,
       (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = 
            Products.CategoryID) as CategoryName, 
       (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = 
            Products.SupplierID) as SupplierName
FROM Products

Las dos subconsultas correlacionadas, (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) y (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID), son consultas SELECT que devuelven un valor único por producto como una columna adicional en la lista de columnas de la instrucción SELECT externa.

Como alternativa, se puede usar una JOIN para devolver el nombre de categoría y proveedor de cada producto. La consulta siguiente devuelve la misma salida que la anterior, pero usa JOIN en lugar de subconsultas:

SELECT ProductID, ProductName, Products.SupplierID, Products.CategoryID, 
       QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, 
       ReorderLevel, Discontinued,
       Categories.CategoryName, 
       Suppliers.CompanyName as SupplierName
FROM Products
    LEFT JOIN Categories ON
        Categories.CategoryID = Products.CategoryID
    LEFT JOIN Suppliers ON
        Suppliers.SupplierID = Products.SupplierID

Una JOIN combina los registros de una tabla con los registros de otra en función de algunos criterios. En la consulta anterior, por ejemplo, LEFT JOIN Categories ON Categories.CategoryID = Products.CategoryID indica a SQL Server que combine cada registro de producto con el registro de categoría cuyo valor de CategoryID coincida con el valor de CategoryID del producto. El resultado combinado nos permite trabajar con los campos de categoría correspondientes para cada producto (por ejemplo, CategoryName).

Nota:

Las JOIN se usan normalmente al consultar datos de bases de datos relacionales. Si no está familiarizado con la sintaxis de JOIN o necesita repasar su uso, recomiendo el tutorial SQL Join en W3 Schools. También merece la pena leer las secciones Aspectos básicos de JOIN y Aspectos básicos de las subconsultas de los Manuales de SQL online.

Como las JOIN y las subconsultas correlacionadas se pueden usar para recuperar datos relacionados de otras tablas, no son pocos los desarrolladores que tienen dudas sobre qué enfoque usar. Todos los gurús de SQL con los que he hablado han dicho aproximadamente lo mismo, que realmente no importa en términos de rendimiento, ya que SQL Server generará planes de ejecución aproximadamente idénticos. Su consejo es usar la técnica con la que usted y su equipo se sientan más cómodos. Merece la pena señalar que, después de impartir este consejo, estos expertos expresan inmediatamente su preferencia por JOIN frente a las subconsultas correlacionadas.

Cuando se compila una capa de acceso a datos con conjuntos de datos con tipo, las herramientas funcionan mejor cuando se usan subconsultas. En concreto, el asistente de TableAdapter no generará automáticamente las instrucciones INSERT, UPDATE y DELETE correspondientes si la consulta principal contiene JOIN, pero sí las generará automáticamente cuando se usen subconsultas correlacionadas.

Para explorar esta limitación, cree un conjunto de datos con tipo temporal en la carpeta ~/App_Code/DAL. Durante el Asistente para configuración de TableAdapter, elija usar instrucciones SQL ad hoc y escriba la siguiente consulta SELECT (vea la figura 1):

SELECT ProductID, ProductName, Products.SupplierID, Products.CategoryID, 
       QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, 
       ReorderLevel, Discontinued,
       Categories.CategoryName, 
       Suppliers.CompanyName as SupplierName
FROM Products
    LEFT JOIN Categories ON
        Categories.CategoryID = Products.CategoryID
    LEFT JOIN Suppliers ON
        Suppliers.SupplierID = Products.SupplierID

Screenshot showing the TableAdaptor Configuration Wizard window with a query entered that contains JOINs.

Figura 1: Escribir una consulta principal que contenga JOIN (haga clic aquí para ver la imagen a tamaño completo)

De forma predeterminada, TableAdapter creará automáticamente instrucciones INSERT, UPDATE y DELETE basadas en la consulta principal. Si hace clic en el botón Opciones avanzadas, puede ver que esta característica está habilitada. A pesar de este ajuste, TableAdapter no podrá crear las instrucciones INSERT, UPDATE y DELETE porque la consulta principal contiene una JOIN.

Screenshot showing the Advanced Options window with the Generate Insert, Update and Delete statements checkbox selected.

Figura 2: Escribir una consulta principal que contenga JOIN

Haga clic en Finalizar para completar el asistente. En este punto, el Diseñador del conjunto de datos incluirá un solo TableAdapter con una DataTable con columnas para cada uno de los campos devueltos en la lista de columnas de la consulta SELECT. Esto incluye CategoryName y SupplierName, como se muestra en la figura 3.

The DataTable Includes a Column for Each Field Returned in the Column List

Figura 3: DataTable incluye una columna para cada campo devuelto en la lista de columnas

Aunque DataTable tiene las columnas adecuadas, TableAdapter no tiene valores para sus propiedades InsertCommand, UpdateCommand y DeleteCommand. Para confirmarlo, haga clic en TableAdapter en el Diseñador y, a continuación, vaya a la ventana Propiedades. Allí verá que las propiedades InsertCommand, UpdateCommand y DeleteCommand se establecen en (None).

The InsertCommand, UpdateCommand, and DeleteCommand Properties are Set to (None)

Figura 4: Las propiedades InsertCommand, UpdateCommand y DeleteCommand están establecidas en (None) (haga clic aquí para ver la imagen a tamaño completo)

Para solucionar este problema, podemos proporcionar manualmente las instrucciones SQL y los parámetros de las propiedades InsertCommand, UpdateCommand y DeleteCommand a través de la ventana Propiedades. Como alternativa, podríamos empezar configurando la consulta principal de TableAdapter para que no incluya ninguna JOIN. Esto permitirá que las instrucciones INSERT, UPDATE y DELETE se generen automáticamente para nosotros. Después de completar el asistente, podemos actualizar manualmente SelectCommand de TableAdapter en la ventana Propiedades para que incluya la sintaxis de JOIN.

Aunque este enfoque funciona, es muy frágil cuando se usan consultas SQL ad hoc, porque cada vez que se vuelve a configurar la consulta principal de TableAdapter con el asistente, se vuelven a crear las instrucciones INSERT, UPDATE y DELETE generadas automáticamente. Esto significa que todas las personalizaciones realizadas después se perderán si hacemos clic con el botón derecho en TableAdapter, elegimos Configurar en el menú contextual y volvemos a completar el asistente.

La fragilidad de las instrucciones INSERT, UPDATE y DELETE generadas automáticamente por TableAdapter se limita afortunadamente a las instrucciones SQL ad hoc. Si TableAdapter usa procedimientos almacenados, puede personalizar los procedimientos almacenados SelectCommand, InsertCommand, UpdateCommand o DeleteCommand y volver a ejecutar el Asistente para configuración de TableAdapter sin temer que se modifiquen los procedimientos almacenados.

En los siguientes pasos crearemos un TableAdapter que, inicialmente, usa una consulta principal que omite cualquier JOIN para que se generen automáticamente los procedimientos almacenados de inserción, actualización y eliminación correspondientes. A continuación, actualizaremos SelectCommand para que use una JOIN que devuelva columnas adicionales de las tablas relacionadas. Por último, crearemos una clase de capa de lógica empresarial correspondiente y mostraremos el uso de TableAdapter en una página web ASP.NET.

Paso 1: Crear un TableAdapter utilizando una consulta principal simplificada

En este tutorial, añadiremos un TableAdapter y una DataTable fuertemente tipada para la tabla Employees del conjunto de datos NorthwindWithSprocs. La tabla Employees contiene un campo ReportsTo que especifica el EmployeeID del director del empleado. Por ejemplo, la empleada Anne Dodsworth tiene un valor de ReportTo igual a 5, que es el EmployeeID de Steven Buchanan. Por lo tanto, Anne responde ante Steven, que es su director. Además de obtener el valor ReportsTo de cada empleado, es posible que también deseemos recuperar el nombre de su jefe. Esto se puede lograr utilizando una JOIN. No obstante, el uso de una JOIN al crear inicialmente el TableAdapter impide que el asistente genere automáticamente las funcionalidades de inserción, actualización y eliminación correspondientes. Por lo tanto, empezaremos creando un TableAdapter cuya consulta principal no contenga ninguna JOIN. A continuación, en el paso 2, actualizaremos el procedimiento almacenado de la consulta principal para recuperar el nombre del director utilizando una JOIN.

Para empezar, abra el conjunto de datos NorthwindWithSprocs en la carpeta ~/App_Code/DAL. Haga clic con el botón derecho en el Diseñador, seleccione la opción Añadir en el menú contextual y elija el elemento de menú TableAdapter. Se iniciará el Asistente para configuración de TableAdapter. Como se muestra en la figura 5, solicite al asistente que cree nuevos procedimientos almacenados y haga clic en Siguiente. Para repasar cómo crear nuevos procedimientos almacenados en el asistente de TableAdapter, consulte el tutorial Creación de nuevos procedimientos almacenados para los TableAdapters del conjunto de datos con tipo.

Select the Create new stored procedures Option

Figura 5: Seleccionar la opción Crear nuevos procedimientos almacenados (haga clic aquí para ver la imagen a tamaño completo)

Use la siguiente instrucción SELECT para la consulta principal de TableAdapter:

SELECT EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country
FROM Employees

Como esta consulta no incluye ninguna JOIN, el asistente de TableAdapter creará automáticamente procedimientos almacenados con las instrucciones INSERT, UPDATE y DELETE correspondientes, así como un procedimiento almacenado para ejecutar la consulta principal.

El siguiente paso nos permite asignar un nombre a los procedimientos almacenados de TableAdapter. Use los nombres Employees_Select, Employees_Insert, Employees_Update y Employees_Delete, como se muestra en la figura 6.

Name the TableAdapter s Stored Procedures

Figura 6: Asignar un nombre a los procedimientos almacenados de TableAdapter (haga clic aquí para ver la imagen a tamaño completo)

El paso final nos solicita que asignemos un nombre a los métodos de TableAdapter. Utilice Fill y GetEmployees como nombres de método. Asegúrese también de dejar activada la casilla Crear métodos para enviar actualizaciones directamente a la base de datos (GenerateDBDirectMethods).

Name the TableAdapter s Methods Fill and GetEmployees

Figura 7: Asignar un nombre a los métodos Fill y GetEmployees de TableAdapter (haga clic aquí para ver la imagen a tamaño completo)

Después de completar el asistente, dedique un momento a examinar los procedimientos almacenados de la base de datos. Debería ver cuatro nuevos: Employees_Select, Employees_Insert, Employees_Update y Employees_Delete. A continuación, inspeccione las propiedades EmployeesDataTable y EmployeesTableAdapter que acaba de crear. DataTable contiene una columna para cada campo devuelto por la consulta principal. Haga clic en TableAdapter y, a continuación, vaya a la ventana Propiedades. Allí verá que las propiedades InsertCommand, UpdateCommand y DeleteCommand están configuradas correctamente para llamar a los procedimientos almacenados correspondientes.

The TableAdapter Includes Insert, Update, and Delete Capabilities

Figura 8: TableAdapter incluye funcionalidades de inserción, actualización y eliminación (haga clic aquí para ver la imagen a tamaño completo)

Con los procedimientos almacenados de inserción, actualización y eliminación creados automáticamente y las propiedades InsertCommand, UpdateCommand y DeleteCommand configuradas correctamente, estamos listos para personalizar el procedimiento almacenado de SelectCommand para devolver información adicional sobre el director de cada empleado. En concreto, es necesario actualizar el procedimiento almacenado Employees_Select para usar una JOIN y devolver los valores FirstName y LastName del director. Una vez actualizado el procedimiento almacenado, es necesario actualizar DataTable para que incluya estas columnas adicionales. Abordaremos estas dos tareas en los pasos 2 y 3.

Paso 2: Personalizar el procedimiento almacenado para incluir una JOIN

Para empezar, vaya al Explorador de servidores, desplácese a la carpeta Procedimientos almacenados de la base de datos Northwind y abra el procedimiento almacenado Employees_Select. Si no ve este procedimiento almacenado, haga clic con el botón derecho en la carpeta Procedimientos almacenados y elija Actualizar. Actualice el procedimiento almacenado para que use LEFT JOIN para devolver el nombre y el apellido del director:

SELECT Employees.EmployeeID, Employees.LastName, 
       Employees.FirstName, Employees.Title, 
       Employees.HireDate, Employees.ReportsTo, 
       Employees.Country,
       Manager.FirstName as ManagerFirstName, 
       Manager.LastName as ManagerLastName
FROM Employees
    LEFT JOIN Employees AS Manager ON
        Employees.ReportsTo = Manager.EmployeeID

Después de actualizar la instrucción SELECT, para guardar los cambios, vaya al menú Archivo y elija Guardar Employees_Select. Como alternativa, puede hacer clic en el icono Guardar de la barra de herramientas o presionar Ctrl+S. Después de guardar los cambios, haga clic con el botón derecho en el procedimiento almacenado Employees_Select en el Explorador de servidores y elija Ejecutar. Se ejecutará el procedimiento almacenado y se mostrarán sus resultados en la ventana Salida (vea la figura 9).

The Stored Procedures Results are Displayed in the Output Window

Figura 9: Los resultados del procedimiento almacenado se muestran en la ventana Salida (haga clic aquí para ver la imagen a tamaño completo)

Paso 3: Actualizar las columnas de DataTable

En este punto, el procedimiento almacenado Employees_Select devuelve los valores ManagerFirstName y ManagerLastName, pero en EmployeesDataTable faltan estas columnas. Estas columnas que faltan se pueden añadir a DataTable de dos maneras:

  • Manualmente: haga clic con el botón derecho en DataTable en el Diseñador de conjuntos de datos y, en el menú Añadir, elija Columna. A continuación, puede asignar un nombre a la columna y establecer sus propiedades según corresponda.
  • Automáticamente: el Asistente para configuración de TableAdapter actualizará las columnas de DataTable para reflejar los campos devueltos por el procedimiento almacenado SelectCommand. Cuando se usan instrucciones SQL ad hoc, el asistente también eliminará las propiedades InsertCommand, UpdateCommand y DeleteCommand, ya que SelectCommand ahora contiene una JOIN. No obstante, cuando se utilizan procedimientos almacenados, estas propiedades de comando permanecen intactas.

Hemos explorado la adición manual de columnas de DataTable en tutoriales anteriores, incluidos Maestro y detalles mediante una lista con viñetas de registros maestros con un control DataList de detalles y Carga de archivos, y veremos este proceso de nuevo con más detalle en nuestro siguiente tutorial. No obstante, en este tutorial vamos a usar el enfoque automático a través del Asistente para configuración de TableAdapter.

Para empezar, haga clic con el botón derecho en EmployeesTableAdapter y seleccione Configurar en el menú contextual. Se abrirá el Asistente para configuración de TableAdapter, que enumera los procedimientos almacenados que se usan para seleccionar, insertar, actualizar y eliminar, junto con sus valores devueltos y sus parámetros (si los hay). La Figura 10 muestra este asistente. Aquí podemos ver que el procedimiento almacenado Employees_Select ahora devuelve los campos ManagerFirstName y ManagerLastName.

The Wizard Shows the Updated Column List for the Employees_Select Stored Procedure

Figura 10: El asistente muestra la lista de columnas actualizada para el procedimiento almacenado Employees_Select (haga clic aquí para ver la imagen a tamaño completo)

Para finalizar el asistente, haga clic en Instalar. Al volver al Diseñador de conjuntos de datos, EmployeesDataTable incluye dos columnas adicionales: ManagerFirstName y ManagerLastName.

The EmployeesDataTable Contains Two New Columns

Figura 11: EmployeesDataTable contiene dos columnas nuevas (haga clic aquí para ver la imagen a tamaño completo)

Para ilustrar que el procedimiento almacenado Employees_Select actualizado está en vigor y que las funcionalidades de inserción, actualización y eliminación de TableAdapter siguen estando operativas, vamos a crear una página web que permita a los usuarios ver y eliminar empleados. Sin embargo, antes de crear esta página, es necesario crear primero una nueva clase en la capa de lógica empresarial para trabajar con empleados del conjunto de datos NorthwindWithSprocs. En el paso 4, crearemos una clase EmployeesBLLWithSprocs. En el paso 5, usaremos esta clase desde una página ASP.NET.

Paso 4: Implementar la capa de lógica empresarial

Cree un nuevo archivo de clase en la carpeta ~/App_Code/BLL llamado EmployeesBLLWithSprocs.cs. Esta clase imita la semántica de la clase EmployeesBLL existente, solo que esta nueva proporciona menos métodos y usa el conjunto de datos NorthwindWithSprocs (en lugar del conjunto de datos Northwind). Agregue el siguiente código a la clase EmployeesBLLWithSprocs .

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindWithSprocsTableAdapters;
[System.ComponentModel.DataObject]
public class EmployeesBLLWithSprocs
{
    private EmployeesTableAdapter _employeesAdapter = null;
    protected EmployeesTableAdapter Adapter
    {
        get
        {
            if (_employeesAdapter == null)
                _employeesAdapter = new EmployeesTableAdapter();
            return _employeesAdapter;
        }
    }
    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, true)]
    public NorthwindWithSprocs.EmployeesDataTable GetEmployees()
    {
        return Adapter.GetEmployees();
    }
    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Delete, true)]
    public bool DeleteEmployee(int employeeID)
    {
        int rowsAffected = Adapter.Delete(employeeID);
        // Return true if precisely one row was deleted, otherwise false
        return rowsAffected == 1;
    }
}

La propiedad Adapter de la clase EmployeesBLLWithSprocs devuelve una instancia de EmployeesTableAdapter del conjunto de datos NorthwindWithSprocs. Esta se utiliza en los métodos GetEmployees y DeleteEmployee de la clase. El método GetEmployees llama al método GetEmployees correspondiente de EmployeesTableAdapter, que invoca el procedimiento almacenado Employees_Select y rellena sus resultados en una EmployeeDataTable. El método DeleteEmployee llama de forma similar al método Delete de EmployeesTableAdapter, que invoca el procedimiento almacenado Employees_Delete.

Paso 5: Trabajar con los datos en la capa de presentación

Una vez completada la clase EmployeesBLLWithSprocs, estamos listos para trabajar con los datos de los empleados a través de una página ASP.NET. Abra la página JOINs.aspx en la carpeta AdvancedDAL, arrastre un control GridView desde el cuadro de herramientas al Diseñador y establezca su propiedad ID en Employees. A continuación, en la etiqueta inteligente de GridView, enlace la cuadrícula a un nuevo control ObjectDataSource denominado EmployeesDataSource.

Configure ObjectDataSource para que utilice la clase EmployeesBLLWithSprocs y, en las pestañas SELECT y DELETE, asegúrese de que los métodos GetEmployees y DeleteEmployee estén seleccionados en las listas desplegables. Haga clic en Finalizar para completar la configuración de ObjectDataSource.

Configure the ObjectDataSource to Use the EmployeesBLLWithSprocs Class

Figura 12: Configurar ObjectDataSource para usar la clase EmployeesBLLWithSprocs (haga clic aquí para ver la imagen a tamaño completo)

Have the ObjectDataSource Use the GetEmployees and DeleteEmployee Methods

Figura 13: Solicitar a ObjectDataSource que use los métodos GetEmployees y DeleteEmployee (haga clic aquí para ver la imagen a tamaño completo)

Visual Studio añadirá un BoundField a la GridView para cada una de las columnas de EmployeesDataTable. Elimine todos estos BoundFields excepto Title, LastNameFirstName, ManagerFirstName y ManagerLastName, y cambie el nombre de las propiedades HeaderText de los últimos cuatro BoundFields a Apellido, Nombre, Nombre del director y Apellido del director, respectivamente.

Para que los usuarios puedan eliminar empleados en esta página, es necesario realizar dos acciones. En primer lugar, para indicar a GridView que proporcione funcionalidades de eliminación, marque la opción Habilitar eliminación en su etiqueta inteligente. En segundo lugar, cambie la propiedad OldValuesParameterFormatStringde ObjectDataSource del valor establecido por el asistente de ObjectDataSource (original_{0}) a su valor predeterminado ({0}). Después de realizar estos cambios, el marcado declarativo de GridView y ObjectDataSource debe ser similar al siguiente:

<asp:GridView ID="Employees" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="EmployeeID" DataSourceID="EmployeesDataSource">
    <Columns>
        <asp:CommandField ShowDeleteButton="True" />
        <asp:BoundField DataField="Title" 
            HeaderText="Title" 
            SortExpression="Title" />
        <asp:BoundField DataField="LastName" 
            HeaderText="Last Name" 
            SortExpression="LastName" />
        <asp:BoundField DataField="FirstName" 
            HeaderText="First Name" 
            SortExpression="FirstName" />
        <asp:BoundField DataField="ManagerFirstName" 
            HeaderText="Manager's First Name" 
            SortExpression="ManagerFirstName" />
        <asp:BoundField DataField="ManagerLastName" 
            HeaderText="Manager's Last Name" 
            SortExpression="ManagerLastName" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="EmployeesDataSource" runat="server" 
    DeleteMethod="DeleteEmployee" OldValuesParameterFormatString="{0}" 
    SelectMethod="GetEmployees" TypeName="EmployeesBLLWithSprocs">
    <DeleteParameters>
        <asp:Parameter Name="employeeID" Type="Int32" />
    </DeleteParameters>
</asp:ObjectDataSource>

Pruebe la página visitándola a través de un explorador. Como se muestra en la figura 14, la página mostrará cada empleado y el nombre de su director (suponiendo que tenga uno).

The JOIN in the Employees_Select Stored Procedure Returns the Manager s Name

Figura 14: JOIN en el procedimiento almacenado Employees_Select devuelve el nombre del director (haga clic aquí para ver la imagen a tamaño completo)

Al hacer clic en el botón Eliminar, se inicia el flujo de trabajo de eliminación, que culmina en la ejecución del procedimiento almacenado Employees_Delete. Sin embargo, se produce un error en la instrucción DELETE intentada en el procedimiento almacenado debido a una infracción de restricción de clave externa (vea la figura 15). En concreto, cada empleado tiene uno o varios registros en la tabla Orders, lo que provoca un error en la eliminación.

Deleting an Employee That has Corresponding Orders Results in a Foreign Key Constraint Violation

Figura 15: Eliminar un empleado que tenga registros correspondientes da como resultado una infracción de restricción de clave externa (haga clic aquí para ver la imagen a tamaño completo)

Para permitir la eliminación de un empleado, puede:

  • Actualizar la restricción de clave externa a eliminaciones en cascada;
  • Eliminar manualmente los registros de la tabla Orders para los empleados que desee eliminar; o
  • Actualizar el procedimiento almacenado Employees_Delete para eliminar primero los registros relacionados de la tabla Orders antes de eliminar el registro Employees. Hemos analizado esta técnica en el tutorial Uso de procedimientos almacenados existentes para Conjunto de datos con tipo de TableAdapters.

Lo dejo como ejercicio para el lector.

Resumen

Al trabajar con bases de datos relacionales, es habitual que las consultas extraen sus datos de varias tablas relacionadas. Las subconsultas correlacionadas y las JOIN proporcionan dos técnicas diferentes para acceder a los datos de tablas relacionadas en una consulta. En tutoriales anteriores, normalmente hemos utilizado subconsultas correlacionadas porque TableAdapter no puede generar automáticamente las instrucciones INSERT, UPDATE y DELETE para las consultas que implican JOIN. Aunque estos valores se pueden proporcionar manualmente, cuando se usan instrucciones SQL ad hoc, las personalizaciones se sobrescribirán cuando finalice el Asistente para configuración de TableAdapter.

Afortunadamente, los TableAdapters creados utilizando procedimientos almacenados no tienen la misma fragilidad que los creados con instrucciones SQL ad hoc. Por lo tanto, es factible crear un TableAdapter cuya consulta principal utilice una JOIN cuando se usan procedimientos almacenados. En este tutorial, hemos visto cómo crear un TableAdapter de este tipo. Hemos empezado utilizando una consulta SELECT sin JOIN para la consulta principal de TableAdapter, para que los procedimientos almacenados de inserción, actualización y eliminación correspondientes se crearan automáticamente. Con la configuración inicial de TableAdapter completada, hemos aumentado el procedimiento almacenado SelectCommand para usar una JOIN y hemos vuelto a ejecutar el Asistente para configuración de TableAdapter para actualizar las columnas de EmployeesDataTable.

Al volver a ejecutar el Asistente para configuración de TableAdapter, se han actualizado automáticamente las columnas de EmployeesDataTable para reflejar los campos de datos devueltos por el procedimiento almacenado Employees_Select. De manera alternativa, podríamos haber añadido estas columnas manualmente a DataTable. Exploraremos la adición manual de columnas a DataTable en el siguiente tutorial.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Puede ponerse en contacto con él vía mitchell@4GuysFromRolla.com. o a través de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Agradecimientos especiales a

Esta serie de tutoriales fue revisada por muchos revisores de gran ayuda. Los revisores principales de este tutorial fueron Hilton Geisenow, David Suru y Teresa Murphy. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.