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

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, consulta el primer tutorial de la serie

En el tutorial anterior comenzaste a usar el control EntityDataSource para trabajar con datos relacionados. Has mostrado varios niveles de jerarquía y datos editados en las propiedades de navegación. En este tutorial seguirás trabajando con datos relacionados agregando y eliminando relaciones y agregando una nueva entidad que tenga una relación con una entidad existente.

Crearás una página que agrega cursos asignados a los departamentos. Los departamentos ya existen y, al crear un nuevo curso, al mismo tiempo se establecerá una relación entre él y un departamento existente.

Screenshot of the Internet Explorer window, which is showing the Add Courses view with ID, Title, and Credits text fields and a Department dropdown.

También crearás una página que funcione con una relación de varios a varios asignando un instructor a un curso (agregando una relación entre dos entidades que selecciones) o quitando un instructor de un curso (quitando una relación entre dos entidades que selecciones). En la base de datos, agregar una relación entre un instructor y un curso da como resultado que se agregue una nueva fila a la tabla de asociación CourseInstructor; quitar una relación implica eliminar una fila de la tabla de asociación CourseInstructor. Sin embargo, lo haces en Entity Framework estableciendo propiedades de navegación, sin hacer referencia a la tabla CourseInstructor explícitamente.

Screenshot of the Internet Explorer window, which is showing the Assign Instructors to Courses or Remove from Courses view.

Agregar una entidad con una relación a una entidad existente

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

<h2>Add Courses</h2>
    <asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
        ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False"
        EntitySetName="Courses" 
        EnableInsert="True" EnableDelete="True" >
    </asp:EntityDataSource>
    <asp:DetailsView ID="CoursesDetailsView" runat="server" AutoGenerateRows="False"
        DataSourceID="CoursesEntityDataSource" DataKeyNames="CourseID"
        DefaultMode="Insert" oniteminserting="CoursesDetailsView_ItemInserting">
        <Fields>
            <asp:BoundField DataField="CourseID" HeaderText="ID" />
            <asp:BoundField DataField="Title" HeaderText="Title" />
            <asp:BoundField DataField="Credits" HeaderText="Credits" />
            <asp:TemplateField HeaderText="Department">
                <InsertItemTemplate>
                    <asp:EntityDataSource ID="DepartmentsEntityDataSource" runat="server" ConnectionString="name=SchoolEntities"
                        DefaultContainerName="SchoolEntities" EnableDelete="True" EnableFlattening="False"
                        EntitySetName="Departments" EntityTypeFilter="Department">
                    </asp:EntityDataSource>
                    <asp:DropDownList ID="DepartmentsDropDownList" runat="server" DataSourceID="DepartmentsEntityDataSource"
                        DataTextField="Name" DataValueField="DepartmentID"
                        oninit="DepartmentsDropDownList_Init">
                    </asp:DropDownList>
                </InsertItemTemplate>
            </asp:TemplateField>
            <asp:CommandField ShowInsertButton="True" />
        </Fields>
    </asp:DetailsView>

Este marcado crea un control EntityDataSource que selecciona los cursos, que habilita la inserción y que especifica un controlador para el eventoInserting. Usarás el controlador para actualizar la propiedad de navegación Department cuando se cree una nueva entidad Course.

El marcado también crea un control DetailsView que se usará para agregar nuevas entidades Course. El marcado usa campos enlazados para las propiedades Course de la entidad. Debes escribir el valor CourseID porque no es un campo de identificador generado por el sistema. En su lugar, es un número de curso que se debe especificar manualmente cuando se crea el curso.

Se usa un campo de plantilla para la propiedad de navegación Department porque las propiedades de navegación no se pueden usar con controles BoundField. El campo de plantilla proporciona una lista desplegable para seleccionar el departamento. La lista desplegable está enlazada al conjunto de entidades Departments mediante Eval en lugar de Bind, de nuevo porque no puedes enlazar directamente las propiedades de navegación para actualizarlas. Especifica un controlador para el evento DropDownList del control Init para que puedas almacenar una referencia al control para que lo use el código que actualiza la clave externa DepartmentID.

En CoursesAdd.aspx.cs justo después de la declaración de clase parcial, agrega un campo de clase para contener una referencia al control DepartmentsDropDownList:

private DropDownList departmentDropDownList;

Agrega un controlador para el evento DepartmentsDropDownList del control Init para que puedas almacenar una referencia al control. Esto te permite obtener el valor especificado por el usuario y usarlo para actualizar el valor DepartmentID de la entidad Course.

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

Implementa un controlador para el evento Inserting del control DetailsView:

protected void CoursesDetailsView_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
    var departmentID = Convert.ToInt32(departmentDropDownList.SelectedValue);
    e.Values["DepartmentID"] = departmentID;
}

Cuando el usuario hace clic en Insert, el evento Inserting se genera antes de insertar el nuevo registro. El código del controlador obtiene DepartmentID del control DropDownList y lo usa para establecer el valor que se usará para la propiedad DepartmentID de la entidad Course.

Entity Framework se encargará de agregar este curso a la propiedad de navegación Courses de la entidad Department asociada. También agrega el departamento a la propiedad de navegación Department de la entidad Course.

Ejecuta la página.

Screenshot of the Internet Explorer window, which shows the Add Courses view with ID, Title, and Credits text fields and a Department dropdown.

Escribe un identificador, un título, un número de créditos y selecciona un departamento y, a continuación, haz clic en Insertar.

Ejecuta la página Courses.aspx y selecciona el mismo departamento para ver el nuevo curso.

Image03

Trabajar con relaciones de varios a varios

La relación entre el conjunto de entidades Courses y el conjunto de entidades People es una relación de varios a varios. Una entidad Course tiene una propiedad de navegación denominada People que puede contener cero, una o más entidades relacionadas Person (que representan a los instructores asignados para enseñar ese curso). Y una entidad Person tiene una propiedad de navegación denominada Courses que puede contener cero, una o más entidades relacionadas Course (que representan cursos que se asignan al instructor para enseñar). Un instructor podría enseñar varios cursos y un curso podría ser enseñado por varios instructores. En esta sección del tutorial, agregarás y quitarás las relaciones entre las entidades Person y Course mediante la actualización de las propiedades de navegación de las entidades relacionadas.

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

<h2>Assign Instructors to Courses or Remove from Courses</h2>
    <br />
    <asp:EntityDataSource ID="InstructorsEntityDataSource" runat="server" 
        ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False" 
        EntitySetName="People"
        Where="it.HireDate is not null" Select="it.LastName + ', ' + it.FirstMidName AS Name, it.PersonID">
    </asp:EntityDataSource>
    Select an Instructor:
    <asp:DropDownList ID="InstructorsDropDownList" runat="server" DataSourceID="InstructorsEntityDataSource"
        AutoPostBack="true" DataTextField="Name" DataValueField="PersonID"
        OnSelectedIndexChanged="InstructorsDropDownList_SelectedIndexChanged" 
        OnDataBound="InstructorsDropDownList_DataBound">
    </asp:DropDownList>
    <h3>
        Assign a Course</h3>
    <br />
    Select a Course:
    <asp:DropDownList ID="UnassignedCoursesDropDownList" runat="server"
        DataTextField="Title" DataValueField="CourseID">
    </asp:DropDownList>
    <br />
    <asp:Button ID="AssignCourseButton" runat="server" Text="Assign" OnClick="AssignCourseButton_Click" />
    <br />
    <asp:Label ID="CourseAssignedLabel" runat="server" Visible="false" Text="Assignment successful"></asp:Label>
    <br />
    <h3>
        Remove a Course</h3>
    <br />
    Select a Course:
    <asp:DropDownList ID="AssignedCoursesDropDownList" runat="server"
        DataTextField="title" DataValueField="courseiD">
    </asp:DropDownList>
    <br />
    <asp:Button ID="RemoveCourseButton" runat="server" Text="Remove" OnClick="RemoveCourseButton_Click" />
    <br />
    <asp:Label ID="CourseRemovedLabel" runat="server" Visible="false" Text="Removal successful"></asp:Label>

Este marcado crea un control EntityDataSource que recupera el nombre y PersonID de las entidades Person de los instructores. Un control DropDrownList está enlazado al control EntityDataSource. El control DropDownList especifica un controlador para el evento DataBound. Usarás este controlador para enlazar los dos listas desplegables que muestran los cursos.

El marcado también crea el siguiente grupo de controles que se usarán para asignar un curso al instructor seleccionado:

  • Un control DropDownList para seleccionar un curso que se va a asignar. Este control se rellenará con cursos que actualmente no están asignados al instructor seleccionado.
  • Un control Button para iniciar la asignación.
  • Un control Label para mostrar un mensaje de error si se produce un error en la asignación.

Por último, el marcado también crea un grupo de controles que se usarán para quitar un curso del instructor seleccionado.

En InstructorsCourses.aspx.cs, agrega una instrucción using:

using ContosoUniversity.DAL;

Agrega un método para rellenar las dos listas desplegables que muestran cursos:

private void PopulateDropDownLists()
{
    using (var context = new SchoolEntities())
    {
        var allCourses = (from c in context.Courses
                          select c).ToList();

        var instructorID = Convert.ToInt32(InstructorsDropDownList.SelectedValue);
        var instructor = (from p in context.People.Include("Courses")
                          where p.PersonID == instructorID
                          select p).First();

        var assignedCourses = instructor.Courses.ToList();
        var unassignedCourses = allCourses.Except(assignedCourses.AsEnumerable()).ToList();

        UnassignedCoursesDropDownList.DataSource = unassignedCourses;
        UnassignedCoursesDropDownList.DataBind();
        UnassignedCoursesDropDownList.Visible = true;

        AssignedCoursesDropDownList.DataSource = assignedCourses;
        AssignedCoursesDropDownList.DataBind();
        AssignedCoursesDropDownList.Visible = true;
    }
}

Este código obtiene todos los cursos del conjunto de entidades Courses y obtiene los cursos de la propiedad de navegación Courses de la entidad Person para el instructor seleccionado. A continuación, determina qué cursos se asignan a ese instructor y rellena las listas desplegables en consecuencia.

Agrega un controlador para el evento Click del botón Assign:

protected void AssignCourseButton_Click(object sender, EventArgs e)
{
    using (var context = new SchoolEntities())
    {
        var instructorID = Convert.ToInt32(InstructorsDropDownList.SelectedValue);
        var instructor = (from p in context.People
                          where p.PersonID == instructorID
                          select p).First();
        var courseID = Convert.ToInt32(UnassignedCoursesDropDownList.SelectedValue);
        var course = (from c in context.Courses
                      where c.CourseID == courseID
                      select c).First();
        instructor.Courses.Add(course);
        try
        {
            context.SaveChanges();
            PopulateDropDownLists();
            CourseAssignedLabel.Text = "Assignment successful.";
        }
        catch (Exception)
        {
            CourseAssignedLabel.Text = "Assignment unsuccessful.";
            //Add code to log the error.
        }
        CourseAssignedLabel.Visible = true;
    }
}

Este código obtiene la entidad Person del instructor seleccionado, obtiene la entidad Course del curso seleccionado y agrega el curso seleccionado a la propiedad de navegación Courses de la entidad Person del instructor. A continuación, guarda los cambios en la base de datos y vuelve a rellenar las listas desplegables para que los resultados se puedan ver inmediatamente.

Agrega un controlador para el evento Click del botón Remove:

protected void RemoveCourseButton_Click(object sender, EventArgs e)
{
    using (var context = new SchoolEntities())
    {
        var instructorID = Convert.ToInt32(InstructorsDropDownList.SelectedValue);
        var instructor = (from p in context.People
                          where p.PersonID == instructorID
                          select p).First();
        var courseID = Convert.ToInt32(AssignedCoursesDropDownList.SelectedValue);
        var courses = instructor.Courses;
        var courseToRemove = new Course();
        foreach (Course c in courses)
        {
            if (c.CourseID == courseID)
            {
                courseToRemove = c;
                break;
            }
        }
        try
        {
            courses.Remove(courseToRemove);
            context.SaveChanges();
            PopulateDropDownLists();
            CourseRemovedLabel.Text = "Removal successful.";
        }
        catch (Exception)
        {
            CourseRemovedLabel.Text = "Removal unsuccessful.";
            //Add code to log the error.
        }
        CourseRemovedLabel.Visible = true;
    }
}

Este código obtiene la entidad Person del instructor seleccionado, obtiene la entidad Course del curso seleccionado y quita el curso seleccionado de la propiedad de navegación Courses de la entidad Person. A continuación, guarda los cambios en la base de datos y vuelve a rellenar las listas desplegables para que los resultados se puedan ver inmediatamente.

Agrega código al método Page_Load que garantiza que los mensajes de error no estén visibles cuando no hay ningún error que notificar y agrega controladores para los eventos DataBound y SelectedIndexChanged de la lista desplegable de instructores para rellenar las listas desplegables de cursos:

protected void Page_Load(object sender, EventArgs e)
{
    CourseAssignedLabel.Visible = false;
    CourseRemovedLabel.Visible = false;
}

protected void InstructorsDropDownList_DataBound(object sender, EventArgs e)
{
    PopulateDropDownLists();
}

protected void InstructorsDropDownList_SelectedIndexChanged(object sender, EventArgs e)
{
    PopulateDropDownLists();
}

Ejecuta la página.

Screenshot of the Internet Explorer window, which shows the Assign Instructors to Courses or Remove from Courses view with corresponding dropdowns.

Selecciona un instructor. La lista desplegable Asignar un curso muestra los cursos a los que el instructor no enseña y la lista desplegable Quitar un curso muestra los cursos a los que el instructor ya está asignado. En la sección Asignar un curso, selecciona un curso y, a continuación, haz clic en Asignar. El curso se mueve a la lista desplegable Quitar un curso. Selecciona un curso en la sección Quitar un curso y haz clic en Quitar. El curso se mueve a la lista desplegable Asignar un curso.

Ahora has visto algunas formas más de trabajar con datos relacionados. En el siguiente tutorial, aprenderás a usar la herencia en el modelo de datos para mejorar la capacidad de mantenimiento de la aplicación.