Начало работы с Базой данных Entity Framework 4.0 и ASP.NET 4 веб-формы — часть 5

Том Дайкстра

Пример веб-приложения Contoso University наглядно показывает, как создавать приложения ASP.NET Web Forms с помощью Entity Framework 4.0 и Visual Studio 2010. Для информации о серии учебных руководств см. первый учебник этой серии

В предыдущем руководстве вы начали использовать EntityDataSource элемент управления для работы с связанными данными. Вы отображали несколько уровней иерархии и редактировали данные в свойствах навигации. В этом руководстве вы будете продолжать работать с связанными данными, добавляя и удаляя связи, а также добавляя новую сущность, которая имеет отношение к существующей сущности.

Вы создадите страницу, которая добавляет курсы, назначенные отделам. Отделы уже существуют, и при создании нового курса в то же время вы создадите связь между ней и существующим отделом.

Снимок экрана: окно Internet Explorer, показывающее представление

Вы также создадите страницу, которая работает с отношением "многие ко многим", назначив инструктора на курс (добавив связь между двумя выбранными сущностями) или удалив инструктора с курса (удалив связь между двумя выбранными сущностями). В базе данных добавление связи между инструктором и курсом приводит к добавлению CourseInstructor новой строки в таблицу ассоциаций; удаление связи включает удаление строки из таблицы ассоциаций CourseInstructor . Однако это можно сделать в Entity Framework, задав свойства навигации без явного ссылки на таблицу CourseInstructor .

Снимок экрана окна Internet Explorer, в котором отображается вид «Назначение инструкторов курсам или удаление из курсов».

Добавление сущности с отношением к существующей сущности

Создайте новую веб-страницу с именем CoursesAdd.aspx , использующую главную страницу Site.Master , и добавьте следующую разметку в Content элемент управления с именем 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>

Эта разметка создает EntityDataSource элемент управления, который выбирает курсы, который включает вставку и задает обработчик события Inserting . Обработчик будет использоваться для обновления Department свойства навигации при создании новой Course сущности.

Разметка также создает DetailsView элемент управления, чтобы использовать для добавления новых Course сущностей. Разметка использует привязанные поля для Course свойств сущности. Необходимо ввести CourseID значение, так как это не поле с созданным системой идентификатором. Вместо этого это номер курса, который необходимо указать вручную при создании курса.

Вы используете поле шаблона для свойства навигации Department, так как его нельзя использовать с элементами управления BoundField. Поле шаблона предоставляет раскрывающийся список для выбора отдела. Раскрывающийся список привязан к Departments набору сущностей, используя Eval вместо Bind, потому что вы не можете напрямую привязать свойства навигации для их обновления. Вы указываете обработчик для события Init элемента управления DropDownList, чтобы сохранить ссылку на этот элемент управления для использования в коде, который обновляет внешний ключ DepartmentID.

В CoursesAdd.aspx.cs сразу после объявления частичного класса добавьте поле класса для хранения ссылки на элемент управления DepartmentsDropDownList.

private DropDownList departmentDropDownList;

Добавьте обработчик события DepartmentsDropDownList элемента управления Init , чтобы сохранить ссылку на элемент управления. Это позволяет получить введенное пользователем значение и использовать его для обновления DepartmentID значения сущности Course .

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

Добавьте обработчик события DetailsView элемента управления Inserting :

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

Когда пользователь щелкает Insert, событие Inserting возникает перед вставкой новой записи. Код в обработчике получает DepartmentID из DropDownList элемента управления и использует его для задания значения, которое будет использоваться для DepartmentID свойства сущности Course .

Entity Framework будет заботиться о добавлении этого курса в Courses навигационное свойство связанной Department сущности. Он также добавляет отдел Department в навигационное свойство сущности Course.

Запустите страницу.

Снимок экрана окна Internet Explorer, в котором отображается представление

Введите идентификатор, название, ряд кредитов и выберите отдел, а затем нажмите кнопку "Вставить".

Запустите страницу Courses.aspx и выберите тот же отдел, чтобы увидеть новый курс.

Image03

Работа с отношениями "многие ко многим"

Связь между набором сущностей Courses и набором сущностей People — это отношение "многие ко многим". Сущность Course имеет свойство навигации с именем People , которое может содержать ноль, одну или несколько связанных Person сущностей (представляющих инструкторов, назначенных для обучения этому курсу). У сущности Person есть свойство навигации с именем Courses, которое может содержать ноль, одну или несколько связанных сущностей Course (представляющих курсы, назначенные инструктору). Один инструктор может преподавать несколько курсов, и один курс может преподаваться несколькими инструкторами. В этом разделе пошагового руководства вы добавите и удалите связи между PersonCourse сущностями, обновив свойства навигации связанных сущностей.

Создайте новую веб-страницу с именем InstructorsCourses.aspx , использующую главную страницу Site.Master , и добавьте следующую разметку в Content элемент управления с именем 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>

Эта разметка создает управляющий элемент EntityDataSource, который получает имя и PersonID из Person сущностей для инструкторов. Элемент DropDrownList управления привязан к элементу EntityDataSource управления. Контрол DropDownList задает обработчик для события DataBound. Этот обработчик будет использоваться для привязки двух раскрывающихся списков, отображающих курсы.

Разметка также создает следующую группу элементов управления для назначения курса выбранному инструктору:

  • Элемент DropDownList управления для выбора курса назначения. Этот элемент управления будет заполнен курсами, которые в настоящее время не назначены выбранному инструктору.
  • Элемент Button управления для запуска задачи.
  • Элемент Label управления для отображения сообщения об ошибке при сбое назначения.

Наконец, разметка создает также группу контролов для удаления курса от выбранного преподавателя.

В InstructorsCourses.aspx.cs добавьте оператор using:

using ContosoUniversity.DAL;

Добавьте метод для заполнения двух раскрывающихся списков, отображающих курсы:

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;
    }
}

Этот код получает все курсы из набора сущностей Courses и получает курсы из свойства навигации Courses сущности Person для выбранного инструктора. Затем он определяет, какие курсы назначены этому инструктору, и заполняет раскрывающиеся списки соответствующим образом.

Добавьте обработчик события Assign кнопки Click :

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;
    }
}

Этот код получает сущность Person для выбранного инструктора, получает сущность Course для выбранного курса, и добавляет выбранный курс в навигационное свойство Courses сущности Person инструктора. Затем сохраняет изменения в базе данных и обновляет выпадающие списки, чтобы результаты могли быть видны немедленно.

Добавьте обработчик события Remove кнопки Click :

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;
    }
}

Этот код получает сущность Person для выбранного инструктора, извлекает сущность Course для выбранного курса и удаляет выбранный курс из навигационного свойства Courses сущности Person. Затем он сохраняет изменения в базе данных и заполняет раскрывающиеся списки заново, чтобы результаты можно было увидеть немедленно.

Добавьте код в метод Page_Load, который гарантирует, что сообщения об ошибках не отображаются при отсутствии ошибок, и добавьте обработчики событий DataBound и SelectedIndexChanged для раскрывающегося списка инструкторов, чтобы заполнить раскрывающиеся списки курсов.

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();
}

Запустите страницу.

Снимок экрана окна Internet Explorer, в котором показано назначение инструкторов на курсы или удаление с курсов с соответствующими раскрывающимися списками.

Выберите инструктора. В раскрывающемся списке "Назначение курса" отображаются курсы, которым не обучает инструктор, и в раскрывающемся списке "Удалить курс " отображаются курсы, которым уже назначен инструктор. В разделе "Назначение курса " выберите курс и нажмите кнопку "Назначить". Курс переходит в раскрывающийся список "Удалить курс ". Выберите курс в разделе "Удалить курс" и нажмите кнопку "Удалить". Курс переходит в раскрывающийся список "Назначение курса ".

Теперь вы видели несколько способов работы с связанными данными. В следующем руководстве вы узнаете, как использовать наследование в модели данных для улучшения удобства обслуживания приложения.