Introducción a Entity Framework 4.0 Database First y ASP.NET 4 Web Forms: parte 4

Por Tom Dykstra

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. Para obtener información sobre la serie de tutoriales, consulte el primer tutorial de la serie

En el tutorial anterior usó el EntityDataSource control para filtrar, ordenar y agrupar datos. En este tutorial, mostrará y actualizará los datos relacionados.

Creará la página Instructors que muestra una lista de instructores. Al seleccionar un instructor, verá una lista de los cursos que enseña ese instructor. Al seleccionar un curso, verá los detalles del curso y una lista de alumnos inscritos en el curso. Puede editar el nombre del instructor, la fecha de contratación y la asignación de oficina. La asignación de office es un conjunto de entidades independiente al que se accede a través de una propiedad de navegación.

Puede vincular datos maestros a datos detallados en el marcado o en el código. En esta parte del tutorial, usará ambos métodos.

Image01

Cree una nueva página web denominada Instructors.aspx que use la página maestra Site.Master y agregue el marcado siguiente al Content control denominado Content2:

<h2>Instructors</h2>
    <div>
        <asp:EntityDataSource ID="InstructorsEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False" 
            EntitySetName="People"
            Where="it.HireDate is not null" Include="OfficeAssignment" EnableUpdate="True">
        </asp:EntityDataSource>
    </div>

Este marcado crea un control EntityDataSource que selecciona instructores y habilita las actualizaciones. El div elemento configura el marcado para que se represente a la izquierda para poder agregar una columna a la derecha más adelante.

Entre el EntityDataSource marcado y la etiqueta de cierre </div>, agregue el marcado siguiente que crea un GridView control y un Label control que usará para los mensajes de error:

<asp:GridView ID="InstructorsGridView" runat="server" AllowPaging="True" AllowSorting="True"
            AutoGenerateColumns="False" DataKeyNames="PersonID" DataSourceID="InstructorsEntityDataSource"
            OnSelectedIndexChanged="InstructorsGridView_SelectedIndexChanged" 
            SelectedRowStyle-BackColor="LightGray" 
            onrowupdating="InstructorsGridView_RowUpdating">
            <Columns>
                <asp:CommandField ShowSelectButton="True" ShowEditButton="True" />
                <asp:TemplateField HeaderText="Name" SortExpression="LastName">
                    <ItemTemplate>
                        <asp:Label ID="InstructorLastNameLabel" runat="server" Text='<%# Eval("LastName") %>'></asp:Label>,
                        <asp:Label ID="InstructorFirstNameLabel" runat="server" Text='<%# Eval("FirstMidName") %>'></asp:Label>
                    </ItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="InstructorLastNameTextBox" runat="server" Text='<%# Bind("FirstMidName") %>' Width="7em"></asp:TextBox>
                        <asp:TextBox ID="InstructorFirstNameTextBox" runat="server" Text='<%# Bind("LastName") %>' Width="7em"></asp:TextBox>
                    </EditItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Hire Date" SortExpression="HireDate">
                    <ItemTemplate>
                        <asp:Label ID="InstructorHireDateLabel" runat="server" Text='<%# Eval("HireDate", "{0:d}") %>'></asp:Label>
                    </ItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="InstructorHireDateTextBox" runat="server" Text='<%# Bind("HireDate", "{0:d}") %>' Width="7em"></asp:TextBox>
                    </EditItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Office Assignment" SortExpression="OfficeAssignment.Location">
                    <ItemTemplate>
                        <asp:Label ID="InstructorOfficeLabel" runat="server" Text='<%# Eval("OfficeAssignment.Location") %>'></asp:Label>
                    </ItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="InstructorOfficeTextBox" runat="server" 
                        Text='<%# Eval("OfficeAssignment.Location") %>' Width="7em"
                        oninit="InstructorOfficeTextBox_Init"></asp:TextBox>
                    </EditItemTemplate>
                </asp:TemplateField>
            </Columns>
            <SelectedRowStyle BackColor="LightGray"></SelectedRowStyle>
        </asp:GridView>
        <asp:Label ID="ErrorMessageLabel" runat="server" Text="" Visible="false" ViewStateMode="Disabled"></asp:Label>

Este GridView control habilita la selección de filas, resalta la fila seleccionada con un color de fondo gris claro y especifica los controladores (que creará más adelante) para los SelectedIndexChanged eventos y Updating. También especifica PersonID para la DataKeyNames propiedad, de modo que el valor de clave de la fila seleccionada se pueda pasar a otro control que agregará más adelante.

La última columna contiene la asignación de oficina del instructor, que se almacena en una propiedad de navegación de la Person entidad porque procede de una entidad asociada. Observe que el EditItemTemplate elemento especifica Eval en lugar de Bind, ya que el GridView control no puede enlazar directamente a las propiedades de navegación para actualizarlos. Actualizará la asignación de office en el código. Para ello, necesitará una referencia al TextBox control y la guardará en el TextBox evento del Init control.

Después del GridView control es un Label control que se usa para los mensajes de error. La propiedad del Visible control es falsey el estado de vista está desactivado, de modo que la etiqueta solo aparecerá cuando el código lo haga visible en respuesta a un error.

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

using ContosoUniversity.DAL;

Agregue un campo de clase privada inmediatamente después de la declaración de nombre de clase parcial para contener una referencia al cuadro de texto asignación de office.

private TextBox instructorOfficeTextBox;

Agregue un código auxiliar para el SelectedIndexChanged controlador de eventos que agregará código para más adelante. Agregue también un controlador para el evento del control de asignación TextBox de office Init para que pueda almacenar una referencia al control TextBox. Usará esta referencia para obtener el valor especificado por el usuario para actualizar la entidad asociada a la propiedad de navegación.

protected void InstructorsGridView_SelectedIndexChanged(object sender, EventArgs e)
{
}

protected void InstructorOfficeTextBox_Init(object sender, EventArgs e)
{
    instructorOfficeTextBox = sender as TextBox;
}

Usará el GridView evento del Updating control para actualizar la Location propiedad de la entidad asociada OfficeAssignment. Agregue el controlador siguiente para el evento Updating:

protected void InstructorsGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    using (var context = new SchoolEntities())
    {
        var instructorBeingUpdated = Convert.ToInt32(e.Keys[0]);
        var officeAssignment = (from o in context.OfficeAssignments
                                where o.InstructorID == instructorBeingUpdated
                                select o).FirstOrDefault();

        try
        {
            if (String.IsNullOrWhiteSpace(instructorOfficeTextBox.Text) == false)
            {
                if (officeAssignment == null)
                {
                    context.OfficeAssignments.AddObject(OfficeAssignment.CreateOfficeAssignment(instructorBeingUpdated, instructorOfficeTextBox.Text, null));
                }
                else
                {
                    officeAssignment.Location = instructorOfficeTextBox.Text;
                }
            }
            else
            {
                if (officeAssignment != null)
                {
                    context.DeleteObject(officeAssignment);
                }
            }
            context.SaveChanges();
        }
        catch (Exception)
        {
            e.Cancel = true;
            ErrorMessageLabel.Visible = true;
            ErrorMessageLabel.Text = "Update failed.";
            //Add code to log the error.
        }
    }
}

Este código se ejecuta cuando el usuario hace clic en Actualizar en una GridView fila. El código usa LINQ to Entities para recuperar la OfficeAssignment entidad asociada a la entidad actual Person mediante el uso de PersonID la fila seleccionada del argumento event.

A continuación, el código realiza una de las siguientes acciones en función del valor del controlInstructorOfficeTextBox:

  • Si el cuadro de texto tiene un valor y no hay ninguna OfficeAssignment entidad que actualizar, crea una.
  • Si el cuadro de texto tiene un valor y hay una entidad OfficeAssignment, actualiza el valor de la propiedad Location.
  • Si el cuadro de texto está vacío y existe una entidad OfficeAssignment, elimina la entidad.

Después de esto, guarda los cambios en la base de datos. Si se produce una excepción, muestra un mensaje de error.

Ejecute la página.

Image02

Haga clic en Editar y todos los campos cambien a cuadros de texto.

Image03

Cambie cualquiera de estos valores, incluida la asignación deOffice. Haga clic en Actualizar y verá los cambios reflejados en la lista.

Cada instructor puede enseñar uno o varios cursos, por lo que agregará un control EntityDataSource y un GridView control para enumerar los cursos asociados con el instructor que esté seleccionado en el control instructores GridView. Para crear un encabezado y el EntityDataSource control para las entidades courses, agregue el marcado siguiente entre el control de mensaje Label de error y la etiqueta de cierre </div>:

<h3>Courses Taught</h3>
        <asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False" 
            EntitySetName="Courses" 
            Where="@PersonID IN (SELECT VALUE instructor.PersonID FROM it.People AS instructor)">
            <WhereParameters>
                <asp:ControlParameter ControlID="InstructorsGridView" Type="Int32" Name="PersonID" PropertyName="SelectedValue" />
            </WhereParameters>
        </asp:EntityDataSource>

El parámetro Where contiene el valor del instructor PersonID cuya fila está seleccionada en el control InstructorsGridView. La Where propiedad contiene un comando de subselección que obtiene todas las entidades asociadas Person de la propiedad de navegación de People una Course entidad y selecciona la Course entidad solo si una de las entidades asociadas Person contiene el valor seleccionado PersonID.

Para crear el GridView control, agregue el siguiente marcado inmediatamente después del CoursesEntityDataSource control (antes de la etiqueta de cierre </div> ):

<asp:GridView ID="CoursesGridView" runat="server" 
            DataSourceID="CoursesEntityDataSource"
            AllowSorting="True" AutoGenerateColumns="False"
            SelectedRowStyle-BackColor="LightGray" 
            DataKeyNames="CourseID">
            <EmptyDataTemplate>
                <p>No courses found.</p>
            </EmptyDataTemplate>
            <Columns>
                <asp:CommandField ShowSelectButton="True" />
                <asp:BoundField DataField="CourseID" HeaderText="ID" ReadOnly="True" SortExpression="CourseID" />
                <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
                <asp:TemplateField HeaderText="Department" SortExpression="DepartmentID">
                    <ItemTemplate>
                        <asp:Label ID="GridViewDepartmentLabel" runat="server" Text='<%# Eval("Department.Name") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>

Dado que no se mostrará ningún curso si no se selecciona ningún instructor, se incluye un EmptyDataTemplate elemento.

Ejecute la página.

Image04

Seleccione un instructor que tenga uno o varios cursos asignados y el curso o cursos aparecen en la lista. (Nota: aunque el esquema de la base de datos permite varios cursos, en los datos de prueba proporcionados con la base de datos no hay ningún instructor que realmente tenga más de un curso. Puede agregar cursos a la base de datos usted mismo mediante la ventana Explorador de servidores o la página CoursesAdd.aspx, que agregará en un tutorial posterior).

Image05

El CoursesGridView control muestra solo algunos campos de curso. Para mostrar todos los detalles de un curso, usará un DetailsView control para el curso que selecciona el usuario. En Instructors.aspx, agregue el siguiente marcado después de la etiqueta de cierre </div> (asegúrese de colocar este marcado después de la etiqueta div de cierre, no antes de ella):

<div>
        <h3>Course Details</h3>
        <asp:EntityDataSource ID="CourseDetailsEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False" 
            EntitySetName="Courses"
            AutoGenerateWhereClause="False" Where="it.CourseID = @CourseID" Include="Department,OnlineCourse,OnsiteCourse,StudentGrades.Person"
            OnSelected="CourseDetailsEntityDataSource_Selected">
            <WhereParameters>
                <asp:ControlParameter ControlID="CoursesGridView" Type="Int32" Name="CourseID" PropertyName="SelectedValue" />
            </WhereParameters>
        </asp:EntityDataSource>
        <asp:DetailsView ID="CourseDetailsView" runat="server" AutoGenerateRows="False"
            DataSourceID="CourseDetailsEntityDataSource">
            <EmptyDataTemplate>
                <p>
                    No course selected.</p>
            </EmptyDataTemplate>
            <Fields>
                <asp:BoundField DataField="CourseID" HeaderText="ID" ReadOnly="True" SortExpression="CourseID" />
                <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
                <asp:BoundField DataField="Credits" HeaderText="Credits" SortExpression="Credits" />
                <asp:TemplateField HeaderText="Department">
                    <ItemTemplate>
                        <asp:Label ID="DetailsViewDepartmentLabel" runat="server" Text='<%# Eval("Department.Name") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Location">
                    <ItemTemplate>
                        <asp:Label ID="LocationLabel" runat="server" Text='<%# Eval("OnsiteCourse.Location") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="URL">
                    <ItemTemplate>
                        <asp:Label ID="URLLabel" runat="server" Text='<%# Eval("OnlineCourse.URL") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
            </Fields>
        </asp:DetailsView>
    </div>

Este marcado crea un EntityDataSource control enlazado al Courses conjunto de entidades. La Where propiedad selecciona un curso con el CourseID valor de la fila seleccionada en el control courses GridView. El marcado especifica un controlador para el Selected evento, que usará más adelante para mostrar las calificaciones de los alumnos, que es otro nivel inferior en la jerarquía.

En Instructors.aspx.cs, cree el código auxiliar siguiente para el CourseDetailsEntityDataSource_Selected método. (Rellenará este código auxiliar más adelante en el tutorial; por ahora, lo necesitará para que la página se compile y ejecute).

protected void CourseDetailsEntityDataSource_Selected(object sender, EntityDataSourceSelectedEventArgs e)
{
}

Ejecute la página.

Image06

Inicialmente no hay detalles del curso porque no se selecciona ningún curso. Seleccione un instructor que tenga un curso asignado y, a continuación, seleccione un curso para ver los detalles.

Image07

Por último, desea mostrar todos los alumnos inscritos y sus calificaciones para el curso seleccionado. Para ello, usará el Selected evento del EntityDataSource control enlazado al curso DetailsView.

En Instructors.aspx, agregue el marcado siguiente después del DetailsView control :

<h3>Student Grades</h3>
        <asp:ListView ID="GradesListView" runat="server">
            <EmptyDataTemplate>
                <p>No student grades found.</p>
            </EmptyDataTemplate>
            <LayoutTemplate>
                <table border="1" runat="server" id="itemPlaceholderContainer">
                    <tr runat="server">
                        <th runat="server">
                            Name
                        </th>
                        <th runat="server">
                            Grade
                        </th>
                    </tr>
                    <tr id="itemPlaceholder" runat="server">
                    </tr>
                </table>
            </LayoutTemplate>
            <ItemTemplate>
                <tr>
                    <td>
                        <asp:Label ID="StudentLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>' />,
                        <asp:Label ID="StudentFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>' />
                    </td>
                    <td>
                        <asp:Label ID="StudentGradeLabel" runat="server" Text='<%# Eval("Grade") %>' />
                    </td>
                </tr>
            </ItemTemplate>
        </asp:ListView>

Este marcado crea un ListView control que muestra una lista de alumnos y sus calificaciones para el curso seleccionado. No se especifica ningún origen de datos porque enlazará el control en el código. El EmptyDataTemplate elemento proporciona un mensaje para mostrar cuando no se selecciona ningún curso; en ese caso, no hay alumnos que se muestren. El LayoutTemplate elemento crea una tabla HTML para mostrar la lista y ItemTemplate especifica las columnas que se van a mostrar. El identificador de alumno y el grado de estudiante proceden de la StudentGrade entidad y el nombre del alumno procede de la Person entidad que Entity Framework pone a disposición en la Person propiedad de navegación de la StudentGrade entidad.

En Instructors.aspx.cs, reemplace el método de código auxiliar CourseDetailsEntityDataSource_Selected por el código siguiente:

protected void CourseDetailsEntityDataSource_Selected(object sender, EntityDataSourceSelectedEventArgs e)
{
    var course = e.Results.Cast<Course>().FirstOrDefault();
    if (course != null)
    {
        var studentGrades = course.StudentGrades.ToList();
        GradesListView.DataSource = studentGrades;
        GradesListView.DataBind();
    }
}

El argumento event para este evento proporciona los datos seleccionados en forma de una colección, que tendrá cero elementos si no se selecciona nada o un elemento si se selecciona una Course entidad. Si se selecciona una Course entidad, el código usa el First método para convertir la colección en un solo objeto. A continuación, obtiene StudentGrade entidades de la propiedad de navegación, las convierte en una colección y enlaza el GradesListView control a la colección.

Esto es suficiente para mostrar las calificaciones, pero quiere asegurarse de que el mensaje de la plantilla de datos vacía se muestra la primera vez que se muestra la página y siempre que no se seleccione un curso. Para ello, cree el método siguiente, al que llamará desde dos lugares:

private void ClearStudentGradesDataSource()
{
    var emptyStudentGradesList = new List<StudentGrade>();
    GradesListView.DataSource = emptyStudentGradesList;
    GradesListView.DataBind();
}

Llame a este nuevo método desde el Page_Load método para mostrar la plantilla de datos vacía la primera vez que se muestre la página. Y llámalo desde el InstructorsGridView_SelectedIndexChanged método porque ese evento se genera cuando se selecciona un instructor, lo que significa que los nuevos cursos se cargan en el control de cursos GridView y aún no se selecciona ninguno. Estas son las dos llamadas:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        ClearStudentGradesDataSource();                
    }
}
protected void InstructorsGridView_SelectedIndexChanged(object sender, EventArgs e)
{
    ClearStudentGradesDataSource();
}

Ejecute la página.

Image08

Seleccione un instructor que tenga un curso asignado y, a continuación, seleccione el curso.

Image09

Ahora ha visto algunas maneras de trabajar con datos relacionados. En el siguiente tutorial, aprenderá a agregar relaciones entre entidades existentes, a quitar relaciones y a agregar una nueva entidad que tenga una relación con una entidad existente.