Controles de origen de datos

de Microsoft

El control DataGrid de ASP.NET 1.x ha supuesto una gran mejora en el acceso a datos en las aplicaciones web. Pero no era tan fácil de usar como podría haber sido. Todavía se necesitaba una cantidad considerable de código para obtener una funcionalidad útil. Este es el modelo en todos los esfuerzos de acceso a datos en la versión 1.x.

El control DataGrid de ASP.NET 1.x ha supuesto una gran mejora en el acceso a datos en las aplicaciones web. Pero no era tan fácil de usar como podría haber sido. Todavía se necesitaba una cantidad considerable de código para obtener una funcionalidad útil. Este es el modelo en todos los esfuerzos de acceso a datos en la versión 1.x.

En ASP.NET 2.0 esto se soluciona en parte con controles de origen de datos. Los controles de origen de datos de ASP.NET 2.0 proporcionan a los desarrolladores un modelo declarativo para recuperar datos, mostrarlos y editarlos. El propósito de los controles de origen de datos es proporcionar una representación coherente de los datos a los controles enlazados a datos independientemente del origen de esos datos. En el centro de los controles de origen de datos de ASP.NET 2.0 se encuentra la clase abstracta DataSourceControl. La clase DataSourceControl proporciona una implementación base de las interface IDataSource e IListSource; esta última le permite asignar el control de origen de datos como valor DataSource de un control enlazado a datos (mediante la nueva propiedad DataSourceId que se describe más adelante) y exponer los datos contenidos como una lista. Cada lista de datos de un control de origen de datos se expone como un objeto DataSourceView. La interfaz IDataSourceSource proporciona acceso a las instancias de DataSourceView. Por ejemplo, el método GetViewNames devuelve una instancia de ICollection que permite enumerar los objetos DataSourceView asociados a un control de origen de datos determinado y el método GetView permite acceder a una instancia determinada de DataSourceView por nombre.

Los controles de origen de datos no tienen ninguna interfaz de usuario. Se implementan como controles de servidor para que puedan admitir sintaxis declarativa y para que tengan acceso al estado de página si es necesario. Los controles de origen de datos no representan ningún marcado HTML en el cliente.

Nota:

Como verá más adelante, también se obtienen ventajas de almacenamiento en caché al usar controles de origen de datos.

Almacenamiento de cadenas de conexión

Antes de examinar cómo configurar controles de origen de datos, se debe describir una nueva funcionalidad en ASP.NET 2.0 relacionada con las cadena de conexión. En ASP.NET 2.0 se presenta una nueva sección en el archivo de configuración que permite almacenar fácilmente cadenas de conexión que se pueden leer dinámicamente en tiempo de ejecución. La sección <connectionStrings> facilita el almacenamiento de cadenas de conexión.

En el fragmento de código siguiente se agrega una nueva cadena de conexión.

<connectionStrings> <add name="Northwind" connectionString="Data Source=localhost; Integrated Security=SSPI;Initial Catalog=Northwind;" providerName="System.Data.SqlClient" /> </connectionStrings>

Nota:

Como sucede con la sección <appSettings>, la sección <connectionStrings> aparece fuera de la sección <system.web> en el archivo de configuración.

Para usar esta cadena de conexión, puede utilizar la sintaxis siguiente al establecer el atributo ConnectionString de un control de servidor.

ConnectionString="<%$ ConnectionStrings:Northwind%>"

La sección <connectionStrings> también se puede cifrar para que la información confidencial no se exponga. Esa funcionalidad se describirá en un módulo posterior.

Almacenamiento en caché de orígenes de datos

Cada instancia de DataSourceControl proporciona cuatro propiedades para configurar el almacenamiento en caché; EnableCaching, CacheDuration, CacheExpirationPolicy y CacheKeyDependency.

EnableCaching

EnableCaching es una propiedad booleana que determina si el almacenamiento en caché está habilitado o no para el control de origen de datos.

CacheDuration (propiedad)

La propiedad CacheDuration establece el número de segundos que la caché sigue siendo válida. Al establecer esta propiedad en 0, la caché es válida hasta que se invalide explícitamente.

CacheExpirationPolicy (propiedad)

La propiedad CacheExpirationPolicy se puede establecer en Absolute o Sliding. Establecerla en Absolute significa que la cantidad máxima de tiempo que se almacenarán en caché los datos es el número de segundos especificados por la propiedad CacheDuration. Al establecerla en Sliding, se restablece la hora de expiración cuando se realiza cada operación.

CacheKeyDependency (propiedad)

Si se especifica un valor de cadena para la propiedad CacheKeyDependency, ASP.NET configurará una nueva dependencia de caché en función de esa cadena. Esto le permite invalidar explícitamente la caché si cambia o quita cacheKeyDependency.

Importante: Si la suplantación está habilitada y el acceso al origen de datos o el contenido de los datos se basan en la identidad del cliente, se recomienda establecer EnableCaching en False para deshabilitar el almacenamiento en caché. Si el almacenamiento en caché está habilitado en este escenario y un usuario distinto del que ha solicitado originalmente los datos emite una solicitud, no se aplica la autorización al origen de datos. Los datos simplemente se servirán desde la memoria caché.

Control SqlDataSource

El control SqlDataSource permite al desarrollador acceder a los datos almacenados en cualquier base de datos relacional que admita ADO.NET. Puede usar el proveedor System.Data.SqlClient para acceder a una base de datos de SQL Server, el proveedor System.Data.OleDb, el proveedor System.Data.Odbc o el proveedor System.Data.OracleClient para acceder a Oracle. Por tanto, SqlDataSource no solo se usa para acceder a los datos de una base de datos de SQL Server.

A fin de usar SqlDataSource, basta con proporcionar un valor para la propiedad ConnectionString y especificar un comando SQL o un procedimiento almacenado. El control SqlDataSource se encarga de trabajar con la arquitectura de ADO.NET subyacente. Abre la conexión, consulta el origen de datos o ejecuta el procedimiento almacenado, devuelve los datos y, después, cierra la conexión automáticamente.

Nota:

Como la clase DataSourceControl cierra automáticamente la conexión, debe reducir el número de llamadas de cliente generadas al filtrar conexiones de base de datos.

En el fragmento de código siguiente se enlaza un control DropDownList a un control SqlDataSource mediante la cadena de conexión almacenada en el archivo de configuración mostrado antes.

<asp:SqlDataSource id="SqlDataSource1" runat="server" DataSourceMode="DataReader" ConnectionString="<%$ ConnectionStrings:Northwind%>" SelectCommand="SELECT EmployeeID, LastName FROM Employees"> </asp:SqlDataSource><asp:DropDownList id="ListBox1" runat="server" DataTextField="LastName" DataValueField="EmployeeID" DataSourceID="SqlDataSource1"> </asp:DropDownList>

Como se ha mostrado antes, la propiedad DataSourceMode de SqlDataSource especifica el modo para el origen de datos. En el ejemplo anterior, DataSourceMode se establece en DataReader. En ese caso, SqlDataSource devolverá un objeto IDataReader mediante un cursor de solo avance y de solo lectura. El proveedor que se usa controla el tipo de objeto especificado que se devuelve. En este caso, se usa el proveedor System.Data.SqlClient como se especifica en la sección <connectionStrings> del archivo web.config. Por tanto, el objeto que se devuelve será de tipo SqlDataReader. Al especificar un valor DataSourceMode de DataSet, los datos se pueden almacenar en un elemento DataSet en el servidor. Este modo le permite agregar características como las de ordenación, paginación, etc. Si se hubieran enlazado datos de SqlDataSource a un control GridView, se habría elegido el modo DataSet. Pero en el caso de DropDownList, el modo DataReader es la opción correcta.

Nota:

Al almacenar en caché un objeto SqlDataSource o AccessDataSource, la propiedad DataSourceMode se debe establecer en DataSet. Se iniciará una excepción si habilita el almacenamiento en caché con un objeto DataSourceMode de tipo DataReader.

Propiedades de SqlDataSource

A continuación se muestran algunas de las propiedades del control SqlDataSource.

CancelSelectOnNullParameter

Valor booleano que especifica si se cancela un comando select si uno de los parámetros es null. De forma predeterminada es True.

ConflictDetection

Cuando varios usuarios pueden actualizar un origen de datos al mismo tiempo, la propiedad ConflictDetection determina el comportamiento del control SqlDataSource. Esta propiedad se evalúa como uno de los valores de la enumeración ConflictOptions. Esos valores son CompareAllValues y OverwriteChanges. Si se establece en OverwriteChanges, la última persona en escribir datos en el origen de datos sobrescribe los cambios anteriores. Pero si la propiedad ConflictDetection se establece en CompareAllValues, se crean parámetros para las columnas devueltas por SelectCommand y también para contener los valores originales en cada una de esas columnas, lo que permite que SqlDataSource determine si los valores han cambiado o no desde la ejecución de SelectCommand.

DeleteCommand

Establece u obtiene la cadena SQL que se usa al eliminar filas de la base de datos. Puede ser una consulta SQL o un nombre de procedimiento almacenado.

DeleteCommandType

Establece u obtiene el tipo de comando delete, ya sea una consulta SQL (Text) o un procedimiento almacenado (StoredProcedure).

DeleteParameters

Devuelve los parámetros que usa DeleteCommand del objeto SqlDataSourceView asociado al control SqlDataSource.

OldValuesParameterFormatString

Esta propiedad se usa para especificar el formato de los parámetros de valor original cuando la propiedad ConflictDetection está establecida en CompareAllValues. El valor predeterminado es {0}, lo que significa que los parámetros de valor original tendrán el mismo nombre que el parámetro original. Es decir, si el nombre del campo es EmployeeID, el parámetro de valor original sería @EmployeeID.

SelectCommand

Establece u obtiene la cadena SQL que se usa para recuperar datos de la base de datos. Puede ser una consulta SQL o un nombre de procedimiento almacenado.

SelectCommandType

Establece u obtiene el tipo de comando select, ya sea una consulta SQL (Text) o un procedimiento almacenado (StoredProcedure).

SelectParameters

Devuelve los parámetros que usa SelectCommand del objeto SqlDataSourceView asociado al control SqlDataSource.

SortParameterName

Obtiene o establece el nombre de un parámetro de procedimiento almacenado que se usa al ordenar los datos recuperados por el control de origen de datos. Solo es válido cuando SelectCommandType se establece en StoredProcedure.

SqlCacheDependency

Cadena delimitada por punto y coma que especifica las bases de datos y tablas usadas en una dependencia de caché de SQL Server. (Las dependencias de caché de SQL se describirán en un módulo posterior).

UpdateCommand

Establece u obtiene la cadena SQL que se usa al actualizar los datos de la base de datos. Puede ser una consulta SQL o un nombre de procedimiento almacenado.

UpdateCommandType

Establece u obtiene el tipo de comando update, ya sea una consulta SQL (Text) o un procedimiento almacenado (StoredProcedure).

UpdateParameters

Devuelve los parámetros utilizados por UpdateCommand del objeto SqlDataSourceView asociado al control SqlDataSource.

Control AccessDataSource

El control AccessDataSource deriva de la clase SqlDataSource y se usa para enlazar datos a una base de datos de Microsoft Access. La propiedad ConnectionString para el control AccessDataSource es una de solo lectura. En lugar de usar la propiedad ConnectionString, se utiliza la propiedad DataFile para apuntar a la base de datos de Access como se muestra a continuación.

<asp:AccessDataSource id="AccessDataSource1" runat="server" DataFile="~/App_Data/Northwind.mdb"> </asp:AccessDataSource>

AccessDataSource siempre establecerá ProviderName de la instancia base de SqlDataSource en System.Data.OleDb y se conectará a la base de datos mediante el proveedor OLE DB Microsoft.Jet.OLEDB.4.0. No puede usar el control AccessDataSource para conectarse a una base de datos de Access protegida con contraseña. Si tiene que conectarse a una base de datos protegida con contraseña, debe usar el control SqlDataSource.

Nota:

Las bases de datos de Access almacenadas en el sitio web se deben colocar en el directorio App_Data. ASP.NET no permite examinar los archivos de este directorio. Deberá conceder permisos de lectura y escritura a la cuenta de proceso al directorio App_Data al usar bases de datos de Access.

Control XmlDataSource

XmlDataSource se usa para enlazar datos XML a controles enlazados a datos. Puede enlazar a un archivo XML mediante la propiedad DataFile, o bien puede enlazar a una cadena XML mediante la propiedad Data. XmlDataSource expone atributos XML como campos enlazables. En los casos en los que necesite enlazar a valores que no están representados como atributos, deberá usar una transformación XSL. También puede usar expresiones XPath para filtrar datos XML.

Fíjese en el siguiente archivo XML:

<?xml version="1.0" encoding="utf-8" ?> <People> <Person FirstName="Jake" LastName="Stone"> <Address> <Street>345 Maple St.</Street> <City>Redmond</City> <Region>WA</Region> <ZipCode>01434</ZipCode> </Address> <Job> <Title>CEO</Title> <Description>Develops company strategies.</Description> </Job> </Person> <Person FirstName="Jacob" LastName="Ladder"> <Address> <Street>123 Elm St.</Street> <City>Seattle</City> <Region>WA</Region> <ZipCode>11223</ZipCode> </Address> <Job> <Title>Attorney</Title> <Description>Reviews legal issues.</Description> </Job> </Person> <Person FirstName="Angela" LastName="Hound"> <Address> <Street>34 Palm Avenue</Street> <City>Renton</City> <Region>WA</Region> <ZipCode>63910</ZipCode> </Address> <Job> <Title>IT Director</Title> <Description>In charge of corporate network.</Description> </Job> </Person> </People>

Observe que XmlDataSource usa una propiedad XPath de People/Person para filtrar solo en los nodos <Person>. Después, DropDownList enlaza datos al atributo LastName mediante la propiedad DataTextField.

Aunque el control XmlDataSource se usa principalmente para enlazar datos a datos XML de solo lectura, es posible editar el archivo de datos XML. Tenga en cuenta que, en esos casos, la inserción automática, la actualización y la eliminación de información en el archivo XML no se produce automáticamente como sucede con otros controles de origen de datos. En su lugar, tendrá que escribir código para editar manualmente los datos mediante los métodos siguientes del control XmlDataSource.

GetXmlDocument

Recupera un objeto XmlDocument que contiene el código XML recuperado por XmlDataSource.

Guardar

Guarda la instancia de XmlDocument en memoria en el origen de datos.

Es importante tener en cuenta que el método Save solo funcionará cuando se cumplan las dos condiciones siguientes:

  1. XmlDataSource usa la propiedad DataFile para enlazar a un archivo XML en lugar de la propiedad Data para enlazar a datos XML en memoria.
  2. No se especifica ninguna transformación mediante la propiedad Transform o TransformFile.

Tenga en cuenta también que el método Save puede producir resultados inesperados cuando varios usuarios lo llaman simultáneamente.

Control ObjectDataSource

Los controles de origen de datos que se han descrito hasta ahora son excelentes opciones para las aplicaciones de dos niveles donde el control de origen de datos se comunica directamente con el almacén de datos. Pero muchas aplicaciones reales son aplicaciones de varios niveles en las que un control de origen de datos podría necesitar comunicarse con un objeto empresarial que, a su vez, se comunica con la capa de datos. En estas situaciones, es la solución perfecta ObjectDataSource. ObjectDataSource funciona junto con un objeto de origen. El control ObjectDataSource creará una instancia del objeto de origen, llamará al método especificado y eliminará la instancia del objeto dentro del ámbito de una única solicitud, si el objeto tiene métodos de instancia en lugar de métodos estáticos (compartidos en Visual Basic). Por tanto, el objeto debe estar sin estado. Es decir, el objeto debe adquirir y liberar todos los recursos necesarios dentro del intervalo de una sola solicitud. Puede controlar cómo se crea el objeto de origen mediante el evento ObjectCreating del control ObjectDataSource. Puede crear una instancia del objeto de origen y, después, establecer la propiedad ObjectInstance de la clase ObjectDataSourceEventArgs en esa instancia. El control ObjectDataSource usará la instancia que se crea en el evento ObjectCreating en lugar de crear una instancia por sí mismo.

Si el objeto de origen de un control ObjectDataSource expone métodos estáticos públicos (compartidos en Visual Basic) a los que se puede llamar para recuperar y modificar datos, un control ObjectDataSource los llamará directamente. Si un control ObjectDataSource debe crear una instancia del objeto de origen para realizar llamadas a métodos, el objeto debe incluir un constructor público que no tome parámetros. El control ObjectDataSource llamará a este constructor cuando cree una instancia del objeto de origen.

Si el objeto de origen no contiene un constructor público sin parámetros, puede crear una instancia del objeto de origen que usará el control ObjectDataSource en el evento ObjectCreating.

Especificación de métodos de objeto

El objeto de origen de un control ObjectDataSource puede contener cualquier número de métodos que se usan para seleccionar, insertar, actualizar o eliminar datos. El control ObjectDataSource llama a estos métodos en función del nombre del método, como se identifica mediante la propiedad SelectMethod, InsertMethod, UpdateMethod o DeleteMethod del control ObjectDataSource. El objeto de origen también puede incluir un método SelectCount opcional, que se identifica mediante el control ObjectDataSource con la propiedad SelectCountMethod, que devuelve el recuento del número total de objetos en el origen de datos. El control ObjectDataSource llamará al método SelectCount después de llamar a un método Select para recuperar el número total de registros en el origen de datos para su uso durante la paginación.

Laboratorio: Uso de controles de origen de datos

Ejercicio 1: Representación de datos con el control SqlDataSource

En el ejercicio siguiente se usa el control SqlDataSource para conectarse a la base de datos Northwind. Se supone que tiene acceso a la base de datos Northwind en una instancia de SQL Server 2000.

  1. Cree un nuevo sitio web de ASP.NET.

  2. Agregue un archivo web.config.

    1. Haga clic con el botón derecho en el Explorador de soluciones y haga clic en Agregar nuevo elemento.
    2. Elija Archivo de configuración web en la lista de plantillas y haga clic en Agregar.
  3. Edite la sección <connectionStrings> como se indica a continuación:

    <asp:SqlDataSource ID="SqlDataSource1" runat="server"
        ConnectionString="<%$ConnectionStrings:Northwind%>"
        SelectCommand="SELECT * FROM Products">
    </asp:SqlDataSource>
    
  4. Cambie a la vista Código y agregue un atributo ConnectionString y un atributo SelectCommand al control <asp:SqlDataSource> de la siguiente manera:

    <asp:SqlDataSource ID="SqlDataSource1" runat="server"
        ConnectionString="<%$ConnectionStrings:Northwind%>"
        SelectCommand="SELECT * FROM Products">
    </asp:SqlDataSource>
    
  5. En la vista Diseño, agregue un nuevo control GridView.

  6. En la lista desplegable Elegir origen de datos del menú Tareas de GridView, elija SqlDataSource1.

  7. Haga clic con el botón derecho en Default.aspx y elija Ver en explorador en el menú. Haga clic en Sí cuando se le solicite.

  8. GridView muestra los datos de la tabla Products.

Ejercicio 2: Edición de datos con el control SqlDataSource

En el ejercicio siguiente se muestra cómo enlazar datos a un control DropDownList mediante la sintaxis declarativa y le permite editar los datos presentados en el control DropDownList.

  1. En la vista Diseño, elimine el control GridView de Default.aspx.

    Importante: Deje el control SqlDataSource en la página.

  2. Agregue un control DropDownList a Default.aspx.

  3. Cambie a la vista Origen.

  4. Agregue un atributo DataSourceId, DataTextField y DataValueField al control <asp:DropDownList> de la siguiente manera:

    <asp:DropDownList ID="ddlProducts" runat="server"
         DataSourceId="SqlDataSource1" DataTextField="ProductName"
         DataValueField="ProductID">
    </asp:DropDownList>
    
  5. Guarde Default.aspx y ábralo en el explorador. Tenga en cuenta que DropDownList contiene todos los productos de la base de datos Northwind.

  6. Cierre el explorador.

  7. En la vista Origen de Default.aspx, agregue un nuevo control TextBox debajo del control DropDownList. Cambie la propiedad ID de TextBox a txtProductName.

  8. En el control TextBox, agregue un nuevo control Button. Cambie la propiedad ID del botón a btnUpdate y la propiedad Text a Actualizar nombre del producto.

  9. En la vista Origen de Default.aspx, agregue una propiedad UpdateCommand y dos nuevas instancias de UpdateParameter a la etiqueta SqlDataSource de la siguiente manera:

    <asp:SqlDataSource ID="SqlDataSource1" runat="server"
        ConnectionString="<%$ConnectionStrings:Northwind%>"
        SelectCommand="SELECT * FROM Products"
        UpdateCommand="UPDATE Products SET ProductName=@ProductName WHERE ProductID=@ProductID">
          <UpdateParameters>
          <asp:ControlParameter Name="ProductName" 
            ControlID="txtProductName" PropertyName="Text" />
          <asp:ControlParameter Name="ProductID" 
            ControlID="ddlProducts" PropertyName="SelectedValue" />
    </asp:SqlDataSource>
    

    Nota:

    Tenga en cuenta que en este código se agregan dos parámetros de actualización (ProductName y ProductID). Estos parámetros se asignan a la propiedad Text del control TextBox txtProductName y a la propiedad SelectedValue de ddlProducts DropDownList.

  10. Cambie a la vista Diseño y haga doble clic en el control Button para agregar un controlador de eventos.

  11. Agregue el código siguiente al código de btnUpdate_Click:

    SqlDataSource1.Update();
    
  12. Haga clic con el botón derecho en Default.aspx y elija verlo en el explorador. Haga clic en Sí cuando se le pida guardar los cambios.

  13. Las clases parciales de ASP.NET 2.0 permiten la compilación en tiempo de ejecución. No es necesario compilar una aplicación para ver que los cambios de código surten efecto.

  14. Seleccione un producto en DropDownList.

  15. Escriba un nombre nuevo para el producto seleccionado en el control TextBox y, después, haga clic en el botón Actualizar.

  16. El nombre del producto se actualiza en la base de datos.

Ejercicio 3: Uso del control ObjectDataSource

En este ejercicio se mostrará cómo usar el control ObjectDataSource y un objeto de origen para interactuar con la base de datos Northwind.

  1. Haga clic con el botón derecho en el Explorador de soluciones y haga clic en Agregar nuevo elemento.

  2. Seleccione Formulario web en la lista de plantillas. Cambie el nombre a object.aspx y haga clic en Agregar.

  3. Haga clic con el botón derecho en el Explorador de soluciones y haga clic en Agregar nuevo elemento.

  4. Seleccione Clase en la lista de plantillas. Cambie el nombre de la clase a NorthwindData.cs y haga clic en Agregar.

  5. Haga clic en Sí cuando se le pida que agregue la clase a la carpeta App_Code.

  6. Agregue el código siguiente al archivo NorthwindData.cs:

    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 System.Data.SqlClient;
    public class NorthwindData {
        private string _connectionString;
        public NorthwindData() {
            Initialize();
        }
    
        private void Initialize() {
            if (ConfigurationManager.ConnectionStrings["Northwind"] == null ||
                ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString.Trim() == "") {
                    throw new Exception("A connection string named 'Northwind' with " +
                    "a valid connection string must exist in the <connectionStrings> " +
                    "configuration section for the application.");
            }
            _connectionString = ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
        }
    
        public DataTable GetAllEmployees(string sortColumns, int startRecord, int maxRecords) {
            VerifySortColumns(sortColumns);
            string sqlCmd = "SELECT EmployeeID, LastName, FirstName, Address, " +
                "City, Region, PostalCode FROM Employees ";
            if (sortColumns.Trim() == "")
                sqlCmd += "ORDER BY EmployeeID";
            else
                sqlCmd += "ORDER BY " + sortColumns;
    
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlDataAdapter da = new SqlDataAdapter(sqlCmd, conn);
            DataSet ds = new DataSet();
            try {
                conn.Open();
                da.Fill(ds, startRecord, maxRecords, "Employees");
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
            return ds.Tables["Employees"];
        }
    
        public int SelectCount() {
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM Employees", conn);
            int result = 0;
    
            try {
                conn.Open();
                result = (int)cmd.ExecuteScalar();
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
            return result;
        }
    
        //////////
        // Verify that only valid columns are specified in the sort expression to
        // avoid a SQL Injection attack.
        private void VerifySortColumns(string sortColumns) {
            if (sortColumns.ToLowerInvariant().EndsWith(" desc"))
                sortColumns = sortColumns.Substring(0, sortColumns.Length - 5);
            string[] columnNames = sortColumns.Split(',');
            foreach (string columnName in columnNames) {
                switch (columnName.Trim().ToLowerInvariant()) {
                    case "employeeid":
                        break;
                    case "lastname":
                        break;
                    case "firstname":
                        break;
                    case "":
                        break;
                    default:
                        throw new ArgumentException("SortColumns contains an " +
                            "invalid column name.");
                        break;
                }
            }
        }
    
        // Select an employee.
        public DataTable GetEmployee(int EmployeeID) {
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlDataAdapter da =
                new SqlDataAdapter("SELECT EmployeeID, LastName, FirstName, " +
                "Address, City, Region, PostalCode " +
                " FROM Employees WHERE EmployeeID = @EmployeeID", conn);
            da.SelectCommand.Parameters.Add("@EmployeeID", SqlDbType.Int).Value = EmployeeID;
            DataSet ds = new DataSet();
            try {
                conn.Open();
                da.Fill(ds, "Employees");
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
    
            return ds.Tables["Employees"];
        }
    
        // Delete the Employee by ID.
        public int DeleteEmployee(int EmployeeID) {
             SqlConnection conn = new SqlConnection(_connectionString);
             SqlCommand cmd = new SqlCommand("DELETE FROM Employees WHERE " +
                 "EmployeeID = @EmployeeID", conn);
             cmd.Parameters.Add("@EmployeeID", SqlDbType.Int).Value = EmployeeID;
             int result = 0;
             try {
                 conn.Open();
                 result = cmd.ExecuteNonQuery();
             } catch (SqlException e) {
                 // Handle exception.
             } finally {
                 conn.Close();
             }
    
             return result;
         }
    
         // Update the Employee by original ID.
         public int UpdateEmployee(int EmployeeID, string LastName, string FirstName,
             string Address, string City, string Region,
             string PostalCode) {
             if (String.IsNullOrEmpty(FirstName))
                 throw new ArgumentException("FirstName cannot be null or an empty string.");
             if (String.IsNullOrEmpty(LastName))
                 throw new ArgumentException("LastName cannot be null or an empty string.");
             if (Address == null) { Address = String.Empty; }
             if (City == null) { City = String.Empty; }
             if (Region == null) { Region = String.Empty; }
             if (PostalCode == null) { PostalCode = String.Empty; }
    
             SqlConnection conn = new SqlConnection(_connectionString);
             SqlCommand cmd = new SqlCommand("UPDATE Employees " +
                 " SET FirstName=@FirstName, " +
                 "LastName=@LastName, " +
                 "Address=@Address, City=@City, " +
                 "Region=@Region, " +
                 "PostalCode=@PostalCode " +
                 "WHERE EmployeeID=@EmployeeID", conn);
             cmd.Parameters.Add("@FirstName", SqlDbType.VarChar, 10).Value = FirstName;
             cmd.Parameters.Add("@LastName", SqlDbType.VarChar, 20).Value = LastName;
             cmd.Parameters.Add("@Address", SqlDbType.VarChar, 60).Value = Address;
             cmd.Parameters.Add("@City", SqlDbType.VarChar, 15).Value = City;
             cmd.Parameters.Add("@Region", SqlDbType.VarChar, 15).Value = Region;
             cmd.Parameters.Add("@PostalCode", SqlDbType.VarChar, 10).Value = PostalCode;
             cmd.Parameters.Add("@EmployeeID", SqlDbType.Int).Value = EmployeeID;
    
             int result = 0;
             try {
                 conn.Open();
                 result = cmd.ExecuteNonQuery();
             } catch (SqlException e) {
                 // Handle exception.
             } finally {
                 conn.Close();
             }
    
             return result;
        }
    
        // Insert an Employee.
        public int InsertEmployee(string LastName, string FirstName,
            string Address, string City, string Region,
            string PostalCode) {
            if (String.IsNullOrEmpty(FirstName))
                throw new ArgumentException("FirstName cannot be null or an empty string.");
            if (String.IsNullOrEmpty(LastName))
                throw new ArgumentException("LastName cannot be null or an empty string.");
            if (Address == null) { Address = String.Empty; }
            if (City == null) { City = String.Empty; }
            if (Region == null) { Region = String.Empty; }
            if (PostalCode == null) { PostalCode = String.Empty; }
    
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlCommand cmd = new SqlCommand("INSERT INTO Employees " +
                " (FirstName, LastName, Address, " +
                " City, Region, PostalCode) " +
                " Values(@FirstName, @LastName, " +
                "@Address, @City, @Region, @PostalCode); " +
                "SELECT @EmployeeID = SCOPE_IDENTITY()", conn);
    
            cmd.Parameters.Add("@FirstName", SqlDbType.VarChar, 10).Value = FirstName;
            cmd.Parameters.Add("@LastName", SqlDbType.VarChar, 20).Value = LastName;
            cmd.Parameters.Add("@Address", SqlDbType.VarChar, 60).Value = Address;
            cmd.Parameters.Add("@City", SqlDbType.VarChar, 15).Value = City;
            cmd.Parameters.Add("@Region", SqlDbType.VarChar, 15).Value = Region;
            cmd.Parameters.Add("@PostalCode", SqlDbType.VarChar, 10).Value = PostalCode;
            SqlParameter p = cmd.Parameters.Add("@EmployeeID", SqlDbType.Int);
                p.Direction = ParameterDirection.Output;
            int newEmployeeID = 0;
            try {
                conn.Open();
                cmd.ExecuteNonQuery();
                newEmployeeID = (int)p.Value;
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
    
            return newEmployeeID;
        }
    
        //
        // Methods that support Optimistic Concurrency checks.
        //
        // Delete the Employee by ID.
        public int DeleteEmployee(int original_EmployeeID, string original_LastName,
            string original_FirstName, string original_Address,
            string original_City, string original_Region,
            string original_PostalCode) {
    
            if (String.IsNullOrEmpty(original_FirstName))
                throw new ArgumentException("FirstName cannot be null or an empty string.");
            if (String.IsNullOrEmpty(original_LastName))
                throw new ArgumentException("LastName cannot be null or an empty string.");
            if (original_Address == null) { original_Address = String.Empty; }
            if (original_City == null) { original_City = String.Empty; }
            if (original_Region == null) { original_Region = String.Empty; }
            if (original_PostalCode == null) { original_PostalCode = String.Empty; }
            string sqlCmd = "DELETE FROM Employees WHERE EmployeeID = " + @original_EmployeeID
    
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlCommand cmd = new SqlCommand(sqlCmd, conn);
            cmd.Parameters.Add("@original_EmployeeID",
                SqlDbType.Int).Value = original_EmployeeID;
            cmd.Parameters.Add("@original_FirstName",
                SqlDbType.VarChar, 10).Value = original_FirstName;
            cmd.Parameters.Add("@original_LastName",
                SqlDbType.VarChar, 20).Value = original_LastName;
            cmd.Parameters.Add("@original_Address",
                SqlDbType.VarChar, 60).Value = original_Address;
            cmd.Parameters.Add("@original_City",
                SqlDbType.VarChar, 15).Value = original_City;
            cmd.Parameters.Add("@original_Region",
                SqlDbType.VarChar, 15).Value = original_Region;
            cmd.Parameters.Add("@original_PostalCode",
                SqlDbType.VarChar, 10).Value = original_PostalCode;
    
            int result = 0;
            try {
                conn.Open();
                result = cmd.ExecuteNonQuery();
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
    
            return result;
        }
    
        // Update the Employee by original ID.
        public int UpdateEmployee(string LastName, string FirstName,
            string Address, string City, string Region,
            string PostalCode, int original_EmployeeID,
            string original_LastName, string original_FirstName,
            string original_Address, string original_City,
            string original_Region, string original_PostalCode) {
    
            if (String.IsNullOrEmpty(FirstName))
                throw new ArgumentException("FirstName cannot be null or an empty string.");
            if (String.IsNullOrEmpty(LastName))
                throw new ArgumentException("LastName cannot be null or an empty string.");
            if (Address == null) { Address = String.Empty; }
            if (City == null) { City = String.Empty; }
            if (Region == null) { Region = String.Empty; }
            if (PostalCode == null) { PostalCode = String.Empty; }
            if (original_Address == null) { original_Address = String.Empty; }
            if (original_City == null) { original_City = String.Empty; }
            if (original_Region == null) { original_Region = String.Empty; }
            if (original_PostalCode == null) { original_PostalCode = String.Empty; }
    
            string sqlCmd = "UPDATE Employees " +
                " SET FirstName = @FirstName, LastName = @LastName, " +
                " Address = @Address, City = @City, Region = @Region, " +
                " PostalCode = @PostalCode " +
                " WHERE EmployeeID = @original_EmployeeID";
    
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlCommand cmd = new SqlCommand(sqlCmd, conn);
            cmd.Parameters.Add("@FirstName", SqlDbType.VarChar, 10).Value = FirstName;
            cmd.Parameters.Add("@LastName", SqlDbType.VarChar, 20).Value = LastName;
            cmd.Parameters.Add("@Address", SqlDbType.VarChar, 60).Value = Address;
            cmd.Parameters.Add("@City", SqlDbType.VarChar, 15).Value = City;
            cmd.Parameters.Add("@Region", SqlDbType.VarChar, 15).Value = Region;
            cmd.Parameters.Add("@PostalCode", SqlDbType.VarChar, 10).Value = PostalCode;
            cmd.Parameters.Add("@original_EmployeeID",
                SqlDbType.Int).Value = original_EmployeeID;
            cmd.Parameters.Add("@original_FirstName",
                SqlDbType.VarChar, 10).Value = original_FirstName;
            cmd.Parameters.Add("@original_LastName",
                SqlDbType.VarChar, 20).Value = original_LastName;
            cmd.Parameters.Add("@original_Address",
                SqlDbType.VarChar, 60).Value = original_Address;
            cmd.Parameters.Add("@original_City",
                SqlDbType.VarChar, 15).Value = original_City;
            cmd.Parameters.Add("@original_Region",
                SqlDbType.VarChar, 15).Value = original_Region;
            cmd.Parameters.Add("@original_PostalCode",
                SqlDbType.VarChar, 10).Value = original_PostalCode;
    
            int result = 0;
    
            try {
                conn.Open();
                result = cmd.ExecuteNonQuery();
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
            return result;
        }
    }
    
  7. Agregue el código siguiente a la vista Origen de object.aspx:

    <%@ Page language="C#" %>
    <script RunAt="server">
    void EmployeesDetailsView_ItemInserted(Object sender, DetailsViewInsertedEventArgs e) {
        EmployeesGridView.DataBind();
    }
    
    void EmployeesDetailsView_ItemUpdated(Object sender, DetailsViewUpdatedEventArgs e) {
        EmployeesGridView.DataBind();
    }
    
    void EmployeesDetailsView_ItemDeleted(Object sender, DetailsViewDeletedEventArgs e) {
        EmployeesGridView.DataBind();
    }
    void EmployeesGridView_OnSelectedIndexChanged(object sender, EventArgs e) {
        EmployeeDetailsObjectDataSource.SelectParameters["EmployeeID"].DefaultValue =
            EmployeesGridView.SelectedDataKey.Value.ToString();
        EmployeesDetailsView.DataBind();
    }
    void EmployeeDetailsObjectDataSource_OnInserted(object sender,
        ObjectDataSourceStatusEventArgs e) {
    
        EmployeeDetailsObjectDataSource.SelectParameters["EmployeeID"].DefaultValue =
            e.ReturnValue.ToString();
        EmployeesDetailsView.DataBind();
    }
    void EmployeeDetailsObjectDataSource_OnUpdated(object sender,
        ObjectDataSourceStatusEventArgs e) {
    
        if ((int)e.ReturnValue == 0)
            Msg.Text = "Employee was not updated. Please try again.";
    }
    void EmployeeDetailsObjectDataSource_OnDeleted(object sender,
        ObjectDataSourceStatusEventArgs e) {
    
        if ((int)e.ReturnValue == 0)
            Msg.Text = "Employee was not deleted. Please try again.";
    }
    void Page_Load() {
        Msg.Text = "";
    }
    </script>
    <html>
      <body>
        <form id="Form1" runat="server">
          <h3>ObjectDataSource Example</h3>
          <asp:Label id="Msg" runat="server" ForeColor="Red" />
          <asp:ObjectDataSource
              ID="EmployeesObjectDataSource"
              runat="server"
              TypeName="NorthwindData"
              SortParameterName="SortColumns"
              EnablePaging="true"
              SelectCountMethod="SelectCount"
              StartRowIndexParameterName="StartRecord"
              MaximumRowsParameterName="MaxRecords"
              SelectMethod="GetAllEmployees" >
          </asp:ObjectDataSource>
          <asp:ObjectDataSource
              ID="EmployeeDetailsObjectDataSource"
              runat="server"
              TypeName="NorthwindData"
              ConflictDetection="CompareAllValues"
              OldValuesParameterFormatString="{0}"
              SelectMethod="GetEmployee"
              InsertMethod="InsertEmployee"
              UpdateMethod="UpdateEmployee"
              DeleteMethod="DeleteEmployee"
              OnInserted="EmployeeDetailsObjectDataSource_OnInserted"
              OnUpdated="EmployeeDetailsObjectDataSource_OnUpdated"
              OnDeleted="EmployeeDetailsObjectDataSource_OnDeleted">
              <SelectParameters>
                  <asp:Parameter Name="EmployeeID" Type="Int32" />
              </SelectParameters>
          </asp:ObjectDataSource>
          <table cellspacing="10">
            <tr>
              <td valign="top">
                <asp:GridView ID="EmployeesGridView"
                    DataSourceID="EmployeesObjectDataSource"
                    AutoGenerateColumns="false"
                    AllowSorting="true"
                    AllowPaging="true"
                    PageSize="5"
                    DataKeyNames="EmployeeID"
                    OnSelectedIndexChanged="EmployeesGridView_OnSelectedIndexChanged"
                    RunAt="server">
                    <HeaderStyle backcolor="lightblue" forecolor="black"/>
                    <Columns>
                    <asp:ButtonField Text="Details..."
                    HeaderText="Show Details"
                    CommandName="Select"/>
    
                    <asp:BoundField DataField="EmployeeID" HeaderText="Employee ID"
                    SortExpression="EmployeeID" />
                    <asp:BoundField DataField="FirstName" HeaderText="First Name"
                    SortExpression="FirstName" />
                    <asp:BoundField DataField="LastName" HeaderText="Last Name"
                    SortExpression="LastName, FirstName" />
                    </Columns>
                </asp:GridView>
              </td>
              <td valign="top">
                <asp:DetailsView ID="EmployeesDetailsView"
                    DataSourceID="EmployeeDetailsObjectDataSource"
                    AutoGenerateRows="false"
                    EmptyDataText="No records."
                    DataKeyNames="EmployeeID"
                    Gridlines="Both"
                    AutoGenerateInsertButton="true"
                    AutoGenerateEditButton="true"
                    AutoGenerateDeleteButton="true"
                    OnItemInserted="EmployeesDetailsView_ItemInserted"
                    OnItemUpdated="EmployeesDetailsView_ItemUpdated"
                    OnItemDeleted="EmployeesDetailsView_ItemDeleted"
                    RunAt="server">
                    <HeaderStyle backcolor="Navy" forecolor="White"/>
                    <RowStyle backcolor="White"/>
                    <AlternatingRowStyle backcolor="LightGray"/>
                    <EditRowStyle backcolor="LightCyan"/>
                    <Fields>
                        <asp:BoundField DataField="EmployeeID" HeaderText="Employee ID"
                            InsertVisible="False" ReadOnly="true"/>
                        <asp:BoundField DataField="FirstName" HeaderText="First Name"/>
                        <asp:BoundField DataField="LastName" HeaderText="Last Name"/>
                        <asp:BoundField DataField="Address" HeaderText="Address"/>
                        <asp:BoundField DataField="City" HeaderText="City"/>
                        <asp:BoundField DataField="Region" HeaderText="Region"/>
                        <asp:BoundField DataField="PostalCode" HeaderText="Postal Code"/>
                    </Fields>
                  </asp:DetailsView>
                </td>
              </tr>
            </table>
          </form>
        </body>
      </html>
    
  8. Guarde todos los archivos y examine object.aspx.

  9. Puede interactuar con la interfaz si ve detalles y edita, agrega y elimina empleados.