Uso de Entity Framework 4.0 y el control ObjectDataSource, parte 1: Introducción

Por Tom Dykstra

Esta serie de tutoriales se basa en la aplicación web Contoso University creada por la Introducción a Entity Framework 4.0 serie de tutoriales. Si no completó los tutoriales anteriores, como punto de partida de este tutorial, puede descargar la aplicación que habría creado. También puede descargar la aplicación creada por la serie de tutoriales completa.

En la aplicación web de ejemplo Contoso University se muestra cómo crear aplicaciones ASP.NET Web Forms con Entity Framework 4.0 y Visual Studio 2010. La aplicación de ejemplo es un sitio web para una universidad ficticia de Contoso. Incluye funciones como la admisión de estudiantes, la creación de cursos y asignaciones de instructores.

En el tutorial se muestran ejemplos en C#. El ejemplo descargable contiene código en C# y Visual Basic.

Database First

Hay tres maneras de trabajar con datos en Entity Framework: Database First, Model First, y Code First. Este tutorial es para Database First. Para obtener información sobre las diferencias entre estos flujos de trabajo e instrucciones sobre cómo elegir la mejor para su escenario, vea Flujos de trabajo de desarrollo de Entity Framework.

Web Forms

Al igual que la serie Introducción, esta serie de tutoriales usa el modelo de formularios ASP.NET Web Forms y supone que sabe cómo trabajar con ASP.NET Web Forms en Visual Studio. Si no lo hace, vea Introducción a ASP.NET 4.5 Web Forms. Si prefiere trabajar con el marco de ASP.NET MVC, vea Introducción a Entity Framework mediante ASP.NET MVC.

Versiones de software

Se muestra en el tutorial También funciona con
Windows 7 Windows 8
Visual Studio 2010 Visual Studio 2010 Express para Web. El tutorial no se ha probado con versiones posteriores de Visual Studio. Hay muchas diferencias en las selecciones de menú, los cuadros de diálogo y las plantillas.
.NET 4 .NET 4.5 es compatible con versiones anteriores con .NET 4, pero el tutorial no se ha probado con .NET 4.5.
Entity Framework 4 El tutorial no se ha probado con versiones posteriores de Entity Framework. A partir de Entity Framework 5, EF usa de forma predeterminada el DbContext API que se introdujo con EF 4.1. El control EntityDataSource se diseñó para usar la ObjectContext API. Para obtener información sobre cómo usar el control EntityDataSource con la DbContext API, vea esta entrada de blog.

Preguntas

Si tiene preguntas que no están directamente relacionadas con el tutorial, puede publicarlas en el Foro de ASP.NET Entity Framework, el Foro de Entity Framework y LINQ to Entities, o StackOverflow.com.

El EntityDataSource control le permite crear una aplicación muy rápidamente, pero normalmente requiere que mantenga una cantidad significativa de lógica de negocios y lógica de acceso a datos en las páginas de .aspx. Si espera que la aplicación crezca en complejidad y requiera mantenimiento continuo, puede invertir más tiempo de desarrollo por adelantado para crear una estructura de aplicaciones de n niveles o superpuestas que sea más fácil de mantener. Para implementar esta arquitectura, se separa la capa de presentación de la capa de lógica de negocios (BLL) y la capa de acceso a datos (DAL). Una manera de implementar esta estructura es usar el ObjectDataSource control en lugar del EntityDataSource control. Cuando se usa el ObjectDataSource control, se implementa su propio código de acceso a datos y, a continuación, se invoca en .aspx páginas mediante un control que tiene muchas de las mismas características que otros controles de origen de datos. Esto le permite combinar las ventajas de un enfoque de n niveles con las ventajas de usar un control de Formularios Web Forms para el acceso a datos.

El ObjectDataSource control también proporciona más flexibilidad de otras maneras. Dado que escribe su propio código de acceso a datos, es más fácil hacer más que leer, insertar, actualizar o eliminar un tipo de entidad específico, que son las tareas que el EntityDataSource control está diseñado para realizar. Por ejemplo, puede realizar el registro cada vez que se actualiza una entidad, archivar los datos cada vez que se elimina una entidad o comprobar y actualizar automáticamente los datos relacionados según sea necesario al insertar una fila con un valor de clave externa.

Clases de lógica de negocios y repositorio

Un ObjectDataSource control funciona invocando una clase que se crea. La clase incluye métodos que recuperan y actualizan datos, y se proporcionan los nombres de esos métodos al control ObjectDataSource en el marcado. Durante la representación o el procesamiento de postback, el ObjectDataSource llama a los métodos especificados.

Además de las operaciones CRUD básicas, es posible que la clase que cree para usarla con el control ObjectDataSource necesite ejecutar lógica de negocios cuando el ObjectDataSource lee o actualiza los datos. Por ejemplo, al actualizar un departamento, es posible que tenga que validar que ningún otro departamento tenga el mismo administrador porque una persona no puede ser administrador de más de un departamento.

En algunas ObjectDataSource documentación, como la información general de la clase ObjectDataSource , el control llama a una clase denominada objeto de negocio que incluye lógica de negocios y lógica de acceso a datos. En este tutorial creará clases independientes para la lógica de negocios y para la lógica de acceso a datos. La clase que encapsula la lógica de acceso a datos se denomina repositorio. La clase lógica de negocios incluye métodos de lógica de negocios y métodos de acceso a datos, pero los métodos de acceso a datos llaman al repositorio para realizar tareas de acceso a datos.

También creará una capa de abstracción entre BLL y DAL que facilita las pruebas unitarias automatizadas de BLL. Esta capa de abstracción se implementa mediante la creación de una interfaz y el uso de la interfaz al crear instancias del repositorio en la clase business-logic. Esto permite proporcionar a la clase lógica de negocios una referencia a cualquier objeto que implemente la interfaz del repositorio. Para una operación normal, se proporciona un objeto de repositorio que funciona con Entity Framework. Para las pruebas, se proporciona un objeto de repositorio que funciona con los datos almacenados de una manera que se puede manipular fácilmente, como las variables de clase definidas como colecciones.

En la ilustración siguiente se muestra la diferencia entre una clase lógica de negocios que incluye lógica de acceso a datos sin un repositorio y otra que usa un repositorio.

Image05

Comenzará creando páginas web en las que el control ObjectDataSource se enlaza directamente a un repositorio porque solo realiza tareas básicas de acceso a datos. En el siguiente tutorial, creará una clase lógica de negocios con lógica de validación y enlazará el control ObjectDataSource a esa clase en lugar de a la clase de repositorio. También creará pruebas unitarias para la lógica de validación. En el tercer tutorial de esta serie, agregará funcionalidad de ordenación y filtrado a la aplicación.

Las páginas que cree en este tutorial funcionan con el Departments conjunto de entidades del modelo de datos que creó en la Serie de tutoriales introducción.

A screenshot that shows what your Departments page should look like.

Image02

Actualización de la base de datos y el modelo de datos

Para comenzar este tutorial, realizará dos cambios en la base de datos, que requieren los cambios correspondientes en el modelo de datos que creó en los tutoriales Introducción a Entity Framework y Web Forms. En uno de esos tutoriales, ha realizado cambios en el diseñador manualmente para sincronizar el modelo de datos con la base de datos después de un cambio en la base de datos. En este tutorial, usará la herramienta Actualizar modelo desde base de datos del diseñador para actualizar el modelo de datos automáticamente.

Agregar una relación a la base de datos

En Visual Studio, abra la aplicación web Contoso University que creó en la serie de tutoriales Introducción a Entity Framework y Web Forms y, a continuación, abra el SchoolDiagram diagrama de base de datos.

Si observa la Department tabla en el diagrama de base de datos, verá que tiene una Administrator columna. Esta columna es una clave externa para la tabla Person, pero no se define ninguna relación de clave externa en la base de datos. Debe crear la relación y actualizar el modelo de datos para que Entity Framework pueda controlar automáticamente esta relación.

En el diagrama de base de datos, haga clic con el botón derecho en la tabla Department y seleccione Relaciones.

Image80

En el cuadro Relaciones de clave externa, haga clic en Agregar y, a continuación, haga clic en los puntos suspensivos de Tablas y especificaciones de columnas.

Image81

En el cuadro de diálogo Tablas y columnas, establezca la tabla y el campo de clave principal en Person y PersonID, y establezca la tabla y el campo de clave externa en Department y Administrator. (Al hacerlo, el nombre de la relación cambiará de FK_Department_Department a FK_Department_Person.)

Image82

Haga clic en Aceptar en el cuadro Tablas y columnas, haga clic en Cerrar en el cuadro Relaciones de clave externa y guarde los cambios. Si se le pregunta si desea guardar las tablas de Person y Department, haga clic en .

Nota:

Si ha eliminado Person filas que corresponden a datos que ya están en la columna Administrator, no podrá guardar este cambio. En ese caso, use el editor de tablas en Explorador de servidores para asegurarse de que el valorAdministrator en cada fila Department contiene el identificador de un registro que realmente existe en la tabla Person.

Después de guardar el cambio, no podrá eliminar una fila de la tabla Person si esa persona es administrador de departamento. En una aplicación de producción, proporcionaría un mensaje de error específico cuando una restricción de base de datos impide una eliminación o especificaría una eliminación en cascada. Para obtener un ejemplo de cómo especificar una eliminación en cascada, vea Entity Framework y ASP.NET – Introducción a la parte 2.

Agregar una vista a la base de datos

En la nueva página de Departments.aspx que va a crear, quiere proporcionar una lista desplegable de instructores, con nombres en formato "last, first" para que los usuarios puedan seleccionar administradores de departamento. Para que sea más fácil hacerlo, creará una vista en la base de datos. La vista constará solo de los datos necesarios en la lista desplegable: el nombre completo (con el formato correcto) y la clave de registro.

En Explorador de servidores, expanda School.mdf, haga clic con el botón derecho en la carpeta Vistas y seleccione Agregar nueva vista.

Image06

Haga clic en Cerrar cuando aparezca el cuadro de diálogo Agregar tabla y pegue la siguiente instrucción SQL en el panel SQL:

SELECT        LastName + ',' + FirstName AS FullName, PersonID
FROM          dbo.Person
WHERE        (HireDate IS NOT NULL)

Guarde la vista como vInstructorName.

Actualización del modelo de datos

En la carpeta DAL, abra el archivo SchoolModel.edmx , haga clic con el botón derecho en la superficie de diseño y seleccione Actualizar modelo en base de datos .

Image07

En el cuadro de diálogo Elegir los objetos de base de datos, seleccione la pestaña Agregar y seleccione la vista que acaba de crear.

Image08

Haga clic en Finalizar

En el diseñador, verá que la herramienta creó una entidad vInstructorName y una nueva asociación entre las entidades Department y Person.

Image13

Nota:

En las ventanas Salida y Lista de errores, es posible que vea un mensaje de advertencia que le informa de que la herramienta creó automáticamente una clave principal para la nueva vInstructorName vista. Este es el comportamiento esperado.

Al hacer referencia a la nueva entidad de vInstructorName en el código, no desea usar la convención de base de datos de prefijo de una "v" en minúsculas. Por lo tanto, cambiará el nombre de la entidad y del conjunto de entidades en el modelo.

Abra el Explorador de modelos. Verá vInstructorName que aparece como un tipo de entidad y una vista.

Image14

En SchoolModel (no SchoolModel.Store), haga clic con el botón derecho en vInstructorName y seleccione Propiedades. En la ventana Propiedades, cambie la propiedad Name a "InstructorName" y cambie la propiedad Entity Set Name a "InstructorNames".

Image15

Guarde y cierre el modelo de datos y vuelva a generar el proyecto.

Usar una clase de repositorio y un control ObjectDataSource

Cree un nuevo archivo de clase en la carpeta DAL, asígnelo el nombre SchoolRepository.cs, y reemplace el código existente por el código siguiente:

using System;
using System.Collections.Generic;
using System.Linq;
using ContosoUniversity.DAL;

namespace ContosoUniversity.DAL
{
    public class SchoolRepository : IDisposable
    {
        private SchoolEntities context = new SchoolEntities();

        public IEnumerable<Department> GetDepartments()
        {
            return context.Departments.Include("Person").ToList();
        }

        private bool disposedValue = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposedValue)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposedValue = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

    }
}

Este código proporciona un único método GetDepartmentsque devuelve todas las entidades del conjunto de entidades Departments. Dado que sabe que va a acceder a la propiedad de navegación Person para cada fila devuelta, especifique la carga diligente de esa propiedad mediante el método Include. La clase también implementa la interfaz IDisposable para asegurarse de que la conexión de base de datos se libera cuando se elimina el objeto.

Nota:

Una práctica habitual es crear una clase de repositorio para cada tipo de entidad. En este tutorial, se usa una clase de repositorio para varios tipos de entidad. Para obtener más información sobre el patrón de repositorio, consulte las entradas del blog del equipo de Entity Framework y blog de Julie Lerman.

El GetDepartments método devuelve un IEnumerable objeto en lugar de un IQueryable objeto para asegurarse de que la colección devuelta se puede usar incluso después de eliminar el propio objeto del repositorio. Un IQueryable objeto puede provocar el acceso a la base de datos cada vez que se tiene acceso a ella, pero el objeto de repositorio podría eliminarse en el momento en que un control de entrada de datos intenta representar los datos. Podría devolver otro tipo de colección, como un objeto IList en lugar de un objeto IEnumerable. Sin embargo, devolver un objeto IEnumerable garantiza que puede realizar tareas típicas de procesamiento de listas de solo lectura, como bucles foreach y consultas LINQ, pero no puede agregar o quitar elementos de la colección, lo que podría implicar que dichos cambios se conservarían en la base de datos.

Cree una página de Departments.aspx que use la página maestraSite.Master y agregue el marcado siguiente en el Content control denominado Content2:

<h2>Departments</h2>
    <asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" 
        DataObjectTypeName="ContosoUniversity.DAL.Department" 
        SelectMethod="GetDepartments" >
    </asp:ObjectDataSource>
    <asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource"  >
        <Columns>
            <asp:CommandField ShowEditButton="True" ShowDeleteButton="True"
                ItemStyle-VerticalAlign="Top">
            </asp:CommandField>
            <asp:DynamicField DataField="Name" HeaderText="Name" SortExpression="Name" ItemStyle-VerticalAlign="Top" />
            <asp:DynamicField DataField="Budget" HeaderText="Budget" SortExpression="Budget" ItemStyle-VerticalAlign="Top" />
            <asp:DynamicField DataField="StartDate" HeaderText="Start Date" ItemStyle-VerticalAlign="Top" />
            <asp:TemplateField HeaderText="Administrator" SortExpression="Person.LastName" ItemStyle-VerticalAlign="Top" >
                <ItemTemplate>
                    <asp:Label ID="AdministratorLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>'></asp:Label>,
                    <asp:Label ID="AdministratorFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>

Este marcado crea un control ObjectDataSource que usa la clase de repositorio que acaba de crear y un control GridView para mostrar los datos. El GridView control especifica comandos Editar y Eliminar, pero aún no ha agregado código para admitirlos.

Varias columnas usan controles DynamicField para que pueda aprovechar las ventajas del formato automático de datos y la funcionalidad de validación. Para que funcionen, tendrá que llamar al método EnableDynamicData en el controlador de eventos Page_Init. (DynamicControl los controles no se usan en el campo Administrator porque no funcionan con las propiedades de navegación.)

Los atributos Vertical-Align="Top" serán importantes más adelante cuando agregue una columna que tenga un control GridView anidado a la cuadrícula.

Abra el archivo Departments.aspx.cs y agregue la siguiente instrucción using:

using ContosoUniversity.DAL;

A continuación, agregue el siguiente controlador para el evento Init de la página:

protected void Page_Init(object sender, EventArgs e)
{
    DepartmentsGridView.EnableDynamicData(typeof(Department));
}

En la carpeta DAL, cree un nuevo archivo de clase denominado Department.cs y reemplace el código existente por el código siguiente:

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.DAL
{
    [MetadataType(typeof(DepartmentMetaData))]
    public partial class Department
    {
    }

    public class DepartmentMetaData
    {
        [DataType(DataType.Currency)]
        [Range(0, 1000000, ErrorMessage = "Budget must be less than $1,000,000.00")]
        public Decimal Budget { get; set; }

        [DisplayFormat(DataFormatString="{0:d}",ApplyFormatInEditMode=true)]
        public DateTime StartDate { get; set; }

    }
}

Este código agrega metadatos al modelo de datos. Especifica que la propiedad Budget de la entidad Department representa realmente la moneda, aunque su tipo de datos es Decimal, y especifica que el valor debe estar comprendido entre 0 y 1.000.000.00. USD. También especifica que la propiedad StartDate debe tener el formato de fecha en el formato mm/dd/aaaa.

Ejecute la página Departments.aspx.

A screenshot that shows the Departments page when it has been run.

Tenga en cuenta que, aunque no especificó una cadena de formato en el marcado de página de Departments.aspx para las columnas Presupuesto o Fecha de inicio, los DynamicField controles han aplicado el formato de moneda y fecha predeterminados mediante los metadatos proporcionados en el archivo Department.cs.

Agregar funcionalidad de inserción y eliminación

Abra SchoolRepository.cs, agregue el código siguiente para crear un método Insert y un método Delete. El código también incluye un método denominado GenerateDepartmentID que calcula el siguiente valor de clave de registro disponible para que lo use el método Insert. Esto es necesario porque la base de datos no está configurada para calcular esto automáticamente para la tabla Department.

public void InsertDepartment(Department department)
{
    try
    {
        department.DepartmentID = GenerateDepartmentID();
        context.Departments.AddObject(department);
        context.SaveChanges();
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

public void DeleteDepartment(Department department)
{
    try
    {
        context.Departments.Attach(department);
        context.Departments.DeleteObject(department);
        context.SaveChanges();
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

private Int32 GenerateDepartmentID()
{
    Int32 maxDepartmentID = 0;
    var department = (from d in GetDepartments()
                      orderby d.DepartmentID descending
                      select d).FirstOrDefault();
    if (department != null)
    {
        maxDepartmentID = department.DepartmentID + 1;
    }
    return maxDepartmentID;
}

Método Attach

El método DeleteDepartment llama al método Attach para volver a establecer el vínculo que se mantiene en el administrador de estado de objetos del contexto de objeto entre la entidad en memoria y la fila de la base de datos que representa. Esto debe ocurrir antes de que el método llame al método SaveChanges.

El término contexto de objeto hace referencia a la clase Entity Framework que deriva de la clase ObjectContext que se usa para tener acceso a los conjuntos de entidades y entidades. En el código de este proyecto, la clase se denomina SchoolEntities, y una instancia de él siempre se denomina context. El administrador de estado de objetos del contexto de objeto es una clase que deriva de la clase ObjectStateManager. El contacto del objeto usa el administrador de estado de objeto para almacenar objetos de entidad y realizar un seguimiento de si cada uno está sincronizado con su fila o filas de tabla correspondientes en la base de datos.

Al leer una entidad, el contexto del objeto lo almacena en el administrador de estado de objetos y realiza un seguimiento de si esa representación del objeto está sincronizada con la base de datos. Por ejemplo, si cambia un valor de propiedad, se establece una marca para indicar que la propiedad que ha cambiado ya no está sincronizada con la base de datos. A continuación, cuando se llama al método SaveChanges, el contexto del objeto sabe qué hacer en la base de datos porque el administrador de estado de objetos sabe exactamente lo que es diferente entre el estado actual de la entidad y el estado de la base de datos.

Sin embargo, este proceso normalmente no funciona en una aplicación web, ya que la instancia de contexto de objeto que lee una entidad, junto con todo lo que hay en su administrador de estado de objetos, se elimina después de representar una página. La instancia de contexto de objeto que debe aplicar cambios es una nueva que se crea una instancia para el procesamiento de postback. En el caso del DeleteDepartment método, el ObjectDataSource control vuelve a crear la versión original de la entidad a partir de valores en estado de vista, pero esta entidad creada de nuevo Department no existe en el administrador de estado de objetos. Si llamó al DeleteObject método en esta entidad de nueva creación, se producirá un error en la llamada porque el contexto del objeto no sabe si la entidad está sincronizada con la base de datos. Sin embargo, al llamar al método Attach se vuelve a establecer el mismo seguimiento entre la entidad que se ha vuelto a crear y los valores de la base de datos que se realizó originalmente cuando la entidad se leyó en una instancia anterior del contexto del objeto.

Hay ocasiones en las que no desea que el contexto del objeto realice un seguimiento de las entidades en el administrador de estado de objetos y puede establecer marcas para evitar que lo haga. Algunos ejemplos de esto se muestran en tutoriales posteriores de esta serie.

El método SaveChanges

Esta clase de repositorio simple ilustra los principios básicos de cómo realizar operaciones CRUD. En este ejemplo, se llama al método SaveChanges inmediatamente después de cada actualización. En una aplicación de producción, es posible que desee llamar al método SaveChanges desde un método independiente para proporcionarle más control sobre cuándo se actualiza la base de datos. (Al final del siguiente tutorial encontrará un vínculo a una notas del producto que describe la unidad de patrón de trabajo que es un enfoque para coordinar las actualizaciones relacionadas). Observe también que en el ejemplo, el método DeleteDepartment no incluye código para controlar conflictos de simultaneidad; código para ello que se agregará en un tutorial posterior de esta serie.

Recuperar nombres de instructor para seleccionar al insertar

Los usuarios deben poder seleccionar un administrador de una lista de instructores en una lista desplegable al crear nuevos departamentos. Por lo tanto, agregue el código siguiente a SchoolRepository.cs para crear un método para recuperar la lista de instructores mediante la vista que creó anteriormente:

public IEnumerable<InstructorName> GetInstructorNames()
{
    return context.InstructorNames.OrderBy("it.FullName").ToList();
}

Crear una página para insertar departamentos

Cree una página de DepartmentsAdd.aspx que use la página de Site.Master y agregue el marcado siguiente en el Contentcontrol denominado Content2:

<h2>Departments</h2>
    <asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" DataObjectTypeName="ContosoUniversity.DAL.Department"
        InsertMethod="InsertDepartment" >
    </asp:ObjectDataSource>
    <asp:DetailsView ID="DepartmentsDetailsView" runat="server" 
        DataSourceID="DepartmentsObjectDataSource" AutoGenerateRows="False"
        DefaultMode="Insert" OnItemInserting="DepartmentsDetailsView_ItemInserting">
        <Fields>
            <asp:DynamicField DataField="Name" HeaderText="Name" />
            <asp:DynamicField DataField="Budget" HeaderText="Budget" />
            <asp:DynamicField DataField="StartDate" HeaderText="Start Date" />
            <asp:TemplateField HeaderText="Administrator">
                <InsertItemTemplate>
                    <asp:ObjectDataSource ID="InstructorsObjectDataSource" runat="server" 
                        TypeName="ContosoUniversity.DAL.SchoolRepository" 
                        DataObjectTypeName="ContosoUniversity.DAL.InstructorName"
                        SelectMethod="GetInstructorNames" >
                    </asp:ObjectDataSource>
                    <asp:DropDownList ID="InstructorsDropDownList" runat="server" 
                        DataSourceID="InstructorsObjectDataSource"
                        DataTextField="FullName" DataValueField="PersonID" OnInit="DepartmentsDropDownList_Init">
                    </asp:DropDownList>
                </InsertItemTemplate>
            </asp:TemplateField>
            <asp:CommandField ShowInsertButton="True" />
        </Fields>
    </asp:DetailsView>
   <asp:ValidationSummary ID="DepartmentsValidationSummary" runat="server" 
        ShowSummary="true" DisplayMode="BulletList"  />

Este marcado crea dos ObjectDataSource controles, uno para insertar nuevas Department entidades y otro para recuperar nombres de instructor para el DropDownList control que se usa para seleccionar administradores de departamento. El marcado crea un DetailsView control para escribir nuevos departamentos y especifica un controlador para el evento del ItemInserting control para que pueda establecer el Administrator valor de clave externa. Al final, se trata de un control ValidationSummary para mostrar mensajes de error.

Abra DepartmentsAdd.aspx.cs y agregue la siguiente instrucción using:

using ContosoUniversity.DAL;

Agregue la siguiente variable de clase y métodos:

private DropDownList administratorsDropDownList;

protected void Page_Init(object sender, EventArgs e)
{
    DepartmentsDetailsView.EnableDynamicData(typeof(Department));
}

protected void DepartmentsDropDownList_Init(object sender, EventArgs e)
{
    administratorsDropDownList = sender as DropDownList;
}

protected void DepartmentsDetailsView_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
    e.Values["Administrator"] = administratorsDropDownList.SelectedValue;
}

El método Page_Init habilita la funcionalidad Datos dinámicos. El controlador del DropDownList evento del Init control guarda una referencia al control y el controlador del DetailsView evento del Inserting control usa esa referencia para obtener el PersonID valor del instructor seleccionado y actualizar la Administrator propiedad de clave externa de la Department entidad.

Ejecute la página, agregue información para un nuevo departamento y haga clic en el vínculo Insertar.

Image04

Escriba los valores de otro nuevo departamento. Escriba un número mayor que 1.000.000.00 en el campo Presupuesto y la pestaña en el campo siguiente. Aparece un asterisco en el campo y, si mantiene el puntero del mouse sobre él, puede ver el mensaje de error que escribió en los metadatos de ese campo.

Image03

Haga clic en Insertar, y verá el mensaje de error que muestra el control ValidationSummary en la parte inferior de la página.

Image12

A continuación, cierre el explorador y abra la página Departments.aspx. Agregue la funcionalidad de eliminación a la página de Departments.aspx agregando un atributo DeleteMethod al control ObjectDataSource y un atributo DataKeyNames al control GridView. Ahora, las etiquetas de apertura de estos controles se asemejarán al ejemplo siguiente:

<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" 
        DataObjectTypeName="ContosoUniversity.DAL.Department"
        SelectMethod="GetDepartments" 
        DeleteMethod="DeleteDepartment" >

    <asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource" DataKeyNames="DepartmentID" >

Ejecute la página.

A screenshot that shows the Departments page after it has been run.

Elimine el departamento que agregó al ejecutar la página de DepartmentsAdd.aspx.

Adición de la funcionalidad de actualización

Abra SchoolRepository.cs y agregue el siguiente método Update:

public void UpdateDepartment(Department department, Department origDepartment)
{
    try
    {
        context.Departments.Attach(origDepartment);
        context.ApplyCurrentValues("Departments", department);
        context.SaveChanges();
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

Al hacer clic en Actualizar en la página Departments.aspx, el ObjectDataSource control crea dos Department entidades para pasar al UpdateDepartment método. Uno contiene los valores originales que se han almacenado en estado de vista y el otro contiene los nuevos valores especificados en el control GridView. El código del método UpdateDepartment pasa la entidad Department que tiene los valores originales al método Attachpara establecer el seguimiento entre la entidad y lo que hay en la base de datos. A continuación, el código pasa la entidad Department que tiene los nuevos valores al método ApplyCurrentValues. El contexto del objeto compara los valores antiguos y nuevos. Si un nuevo valor es diferente de un valor anterior, el contexto del objeto cambia el valor de propiedad. A continuación, el método SaveChanges actualiza solo las columnas modificadas de la base de datos. (Sin embargo, si la función de actualización de esta entidad se asignaba a un procedimiento almacenado, toda la fila se actualizaría independientemente de qué columnas se cambiaran.)

Abra el archivo Departments.aspx y agregue los atributos siguientes al control DepartmentsObjectDataSource:

  • UpdateMethod="UpdateDepartment"
  • ConflictDetection="CompareAllValues"
    Esto hace que los valores antiguos se almacenen en estado de vista para que se puedan comparar con los nuevos valores del método Update.
  • OldValuesParameterFormatString="orig{0}"
    Esto informa al control de que el nombre del parámetro de valores originales es origDepartment.

El marcado de la etiqueta de apertura del control ObjectDataSource ahora es similar al ejemplo siguiente:

<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" 
        DataObjectTypeName="ContosoUniversity.DAL.Department" 
        SelectMethod="GetDepartments" DeleteMethod="DeleteDepartment" 
        UpdateMethod="UpdateDepartment"
        ConflictDetection="CompareAllValues" 
        OldValuesParameterFormatString="orig{0}" >

Agregue un atributo OnRowUpdating="DepartmentsGridView_RowUpdating" al control GridView. Lo usará para establecer el valor de la propiedad Administrator en función de la fila que el usuario selecciona en una lista desplegable. La etiqueta de apertura GridView ahora es similar al ejemplo siguiente:

<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource" DataKeyNames="DepartmentID"
        OnRowUpdating="DepartmentsGridView_RowUpdating">

Agregue un control EditItemTemplate para la columna Administrator al control GridView, inmediatamente después del control ItemTemplate para esa columna:

<EditItemTemplate>
                    <asp:ObjectDataSource ID="InstructorsObjectDataSource" runat="server" DataObjectTypeName="ContosoUniversity.DAL.InstructorName"
                        SelectMethod="GetInstructorNames" TypeName="ContosoUniversity.DAL.SchoolRepository">
                    </asp:ObjectDataSource>
                    <asp:DropDownList ID="InstructorsDropDownList" runat="server" DataSourceID="InstructorsObjectDataSource"
                        SelectedValue='<%# Eval("Administrator")  %>'
                        DataTextField="FullName" DataValueField="PersonID" OnInit="DepartmentsDropDownList_Init" >
                    </asp:DropDownList>
                </EditItemTemplate>

Este control EditItemTemplate es similar al control InsertItemTemplate de la página de DepartmentsAdd.aspx. La diferencia es que el valor inicial del control se establece mediante el SelectedValue atributo.

Antes del control GridView, agregue un control ValidationSummary como hizo en la página DepartmentsAdd.aspx.

<asp:ValidationSummary ID="DepartmentsValidationSummary" runat="server" 
        ShowSummary="true" DisplayMode="BulletList"  />

Abra Departments.aspx.cs e inmediatamente después de la declaración de clase parcial, agregue el código siguiente para crear un campo privado para hacer referencia al control DropDownList:

private DropDownList administratorsDropDownList;

A continuación, agregue controladores para el evento DropDownList del control Init y el evento GridView del control RowUpdating:

protected void DepartmentsDropDownList_Init(object sender, EventArgs e)
{
    administratorsDropDownList = sender as DropDownList;
}

protected void DepartmentsGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    e.NewValues["Administrator"] = administratorsDropDownList.SelectedValue;
}

El controlador del evento Init guarda una referencia al control DropDownList en el campo de clase. El controlador del evento RowUpdating usa la referencia para obtener el valor especificado por el usuario y aplicarlo a la propiedad Administrator de la entidad Department.

Use la página DepartmentsAdd.aspx para agregar un nuevo departamento, ejecute la página de Departments.aspx y haga clic en Editar de la fila que agregó.

Nota:

No podrá editar las filas que no ha agregado (es decir, que ya estaban en la base de datos), debido a datos no válidos en la base de datos; los administradores de las filas que se crearon con la base de datos son estudiantes. Si intenta editar uno de ellos, obtendrá una página de error que notifica un error como 'InstructorsDropDownList' has a SelectedValue which is invalid because it does not exist in the list of items.

Image10

Si escribe un presupuesto no válido y, a continuación, hace clic en Actualizar, verá el mismo asterisco y mensaje de error que vio en la página de Departments.aspx.

Cambie un valor de campo o seleccione otro administrador y haga clic en Actualizar. Se muestra el cambio.

A screenshot that shows the Departments page.

Esto completa la introducción al uso del control ObjectDataSource para las operaciones CRUD básicas (crear, leer, actualizar, eliminar) con Entity Framework. Ha creado una aplicación sencilla de n niveles, pero la capa de lógica de negocios todavía está estrechamente acoplada a la capa de acceso a datos, lo que complica las pruebas unitarias automatizadas. En el siguiente tutorial, verá cómo implementar el patrón de repositorio para facilitar las pruebas unitarias.