Пошаговое руководство по выполнению сериализации сущностей с самостоятельным отслеживанием (платформа Entity Framework)

Пошаговое руководство в данном разделе использует сценарий, в котором служба Windows Communication Foundation (WCF) предоставляет доступ к серии операций, возвращающих графы сущностей. Затем клиентское приложение обрабатывает этот граф и передает изменения операции службы, которая проверяет изменения и сохраняет их в базу данных с помощью платформы Entity Framework. Дополнительные сведения см. в разделе Работа с сущностями с самостоятельным отслеживанием.

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

Один из способов обеспечить эту изоляцию состоит в том, чтобы отделить типы сущности с самостоятельным отслеживанием от модели и переместить шаблон, формирующий типы сущности, в отдельную библиотеку классов. Для доступа к метаданным в шаблон сущностей с самостоятельным отслеживанием следует включить местоположение EDMX-файла. При перемещении шаблона из первоначального проекта в другой необходимо открыть файл шаблона в редакторе и изменить строку inputFile, указав относительный путь к EDMX-файлу. Дополнительные сведения о работе с шаблонами см. в разделе ADO.NET Self-Tracking Entity Template.

Еще один способ разделения типов сущности и модели — это оставить файл шаблона в исходном проекте и отключить создание кода для шаблона. Ссылка указывает на шаблон из другого проекта, поэтому код сформируется в этом другом проекте вместо исходного. При добавлении элемента в проект в виде ссылки актуальный контекст элемента поддерживается в месте, указанном в исходном проекте. Этот метод разделения типов сущности и модели показан в этом пошаговом руководстве.

В этом пошаговом руководстве выполняются следующие действия.

  • Создается проект библиотеки классов, содержащий модель, построенную на основе School.

  • Шаблон генератора сущностей ADO.NET с самостоятельным отслеживанием используется для формирования типов сущности, типизированного контекста ObjectContext и класса расширения, содержащего перегруженные методы ApplyChanges.

  • Создается проект библиотеки классов, связанный с шаблоном типов сущности с самостоятельным отслеживанием, созданным в первом проекте.

  • Создается служба WCF, которая обеспечивает доступ к набору операций, возвращающих графы сущностей, и с помощью Entity Framework применяет изменения, сделанные на клиенте, к базе данных.

  • Создает клиентские приложения (консольное и WPF), которые обрабатывают граф и фиксируют изменения с помощью операций, доступ к которым предоставляет служба WCF.

Ee789839.note(ru-ru,VS.100).gifПримечание
Пример STESchoolModelExample можно загрузить на сайте Entity Framework Documentation Samples MSDN Code Gallery.

Создание проекта библиотеки классов, содержащего сущности с самостоятельным отслеживанием и классы контекста объекта

  1. Создайте новый проект библиотеки классов. Введите STESchoolModel в качестве имени проекта и решения.

  2. Удалите файл исходного кода по умолчанию, добавленный к проекту.

  3. С помощью мастера моделей EDM создайте модель на основе таблиц Department, Course, OnlineCourse и OnsiteCourse в базе данных School. Дополнительные сведения см. в разделе School, модель.

  4. Откройте EDMX-файл в конструкторе сущностей (ADO.NET Entity Data Model Designer).

  5. Следуйте инструкциям по сопоставлению наследования из раздела Walkthrough: Mapping Inheritance - Table-per-Type.

  6. Щелкните правой кнопкой мыши пустой участок области конструктора сущностей, выберите Добавить элемент формирования кода, а затем Генератор сущностей с самостоятельным отслеживанием ADO.NET. Измените имя по умолчанию для шаблона на SchoolModel.

    Ee789839.note(ru-ru,VS.100).gifПримечание
    После добавления файлов шаблонов в проект может появиться предупреждение безопасности, запрашивающее подтверждение надежности источника шаблона.Нажмите кнопку Принять.

  7. Папки SchoolModel.Context.tt и SchoolModel.tt добавляются в проект. В папке SchoolModel.Context.tt содержатся два файла, определяющие типизированный контекст ObjectContext и класс расширения, содержащий перегруженные методы ApplyChanges. В папке SchoolModel.tt находятся файлы, определяющие типы сущности, и вспомогательный класс, содержащий логику отслеживания изменений, используемую сущностями с самостоятельным отслеживанием.

    Следующие два шага иллюстрируют отключение создания кода в данном проекте. Создание кода будет включено позже для типов STESchoolModelTypes в библиотеке классов и для контекста объекта в STESchoolModelService.

  8. Выберите SchoolModel.tt. В окне Свойства удалите TextTemplatingFileGenerator из свойства CustomTool. Удалите файлы в папке SchoolModel.tt.

  9. Выберите SchoolModel.Context.tt. В окне Свойства очистите значение свойства CustomTool. Удалите файлы в папке SchoolModel.Context.tt.

    При работе с проектом Visual Basic, чтобы увидеть все файлы проекта, может потребоваться выбрать Показать все файлы в обозревателе решений.

  10. Скомпилируйте проект.

Создание проекта библиотеки классов, ссылающегося на шаблон типов с самостоятельным отслеживанием

  1. Создайте новый проект библиотеки классов с именем STESchoolModelTypes в том же решении, что и предыдущий проект.

  2. Удалите файл исходного кода по умолчанию, добавленный к проекту.

  3. Добавьте ссылку на файл SchoolModel.tt, чтобы типы сущности с самостоятельным отслеживанием формировались в этом решении. В окне Обозреватель решений щелкните правой кнопкой мыши STESchoolModelTypes, выберите Добавить, затем Существующий элемент.

  4. В диалоговом окне Добавление существующего элемента найдите проект STESchoolModel и щелкните SchoolModel.tt (не нажимайте клавишу ВВОД). В списке Добавить выберите команду Добавить как связь.

  5. Добавьте ссылку на библиотеку System.Runtime.Serialization. Эта библиотека нужна для DataContract WCF и для атрибутов DataMember, используемых для сериализуемых типов сущности.

  6. Скомпилируйте проект.

Создание и конфигурирование проекта приложения службы WCF

  1. Создайте проект приложения службы WCF с именем STESchoolModelService в том же решении, что и предыдущий проект.

  2. Добавьте ссылку на System.Data.Entity.dll.

  3. Добавьте ссылку на проекты STESchoolModel и STESchoolModelTypes.

  4. Добавьте ссылку на файл SchoolModel.Context.tt, чтобы типы контекста формировались в этом решении. В окне Обозреватель решений щелкните правой кнопкой мыши STESchoolModelService, выберите Добавить, затем Существующий элемент.

  5. В диалоговом окне Добавление существующего элемента загрузите проект STESchoolModel и щелкните SchoolModel.Context.tt (не нажимайте клавишу ВВОД). В списке Добавить выберите команду Добавить как связь.

  6. В окне «Свойства» файла SchoolModel.Context.tt введите значение STESchoolModelService для свойства Пространство имен CustomTool. Тип контекста объекта будет добавлен в то же самое пространство имен, где находятся типы сущности с самостоятельным отслеживанием. Это обязательно.

  7. (Только для Visual Basic) Добавьте Import STESchoolModelTypes к файлам исходного кода, созданным на основе файла SchoolModel.Context.tt. Откройте файл SchoolModel.Context.tt и найдите строку Imports System. После других команд импорта добавьте Import STESchoolModelTypes. Сформированные файлы исходного кода будут включать в себя это пространство имен.

  8. Добавьте строку подключения в файл Web.config, чтобы среда выполнения Entity Framework могла найти метаданные. Откройте файл app.config проекта STESchoolModel . Скопируйте элемент connectionStrings, а затем добавьте его как дочерний элемент configuration в файл Web.config.

  9. Откройте файл интерфейса службы. По умолчанию он называется IService1.

  10. Добавьте пространство имен, где определены сущности с самостоятельным отслеживанием: STESchoolModelTypes.

  11. Замените определение интерфейса служб следующим кодом:

    <ServiceContract()> _
    Public Interface IService1
        <OperationContract()> _
        Sub UpdateDepartment(ByVal updated As Department)
        <OperationContract()> _
        Function GetDepartments() As List(Of Department)
    End Interface
    
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        void UpdateDepartment(Department updated);
        [OperationContract]
        List<Department> GetDepartments();
    }
    
  12. Откройте исходный код службы. По умолчанию он называется Service1.srv.cs (или Service1.srv.vb).

  13. Добавьте пространство имен, где определены сущности с самостоятельным отслеживанием: STESchoolModelTypes.

  14. (Только для Visual Basic) Добавьте Imports STESchoolModelService.STESchoolModelTypes в файл Service1.srv.cs.

  15. Замените определение класса служб следующим кодом:

Ee789839.Important(ru-ru,VS.100).gif Примечание
Перед тем как применить изменения, всегда следует проводить проверку измененного объекта.

Public Class Service1
    Implements IService1
    ''' <summary>
    ''' Updates department and its related courses. 
    ''' </summary>
    Public Sub UpdateDepartment(ByVal updated As Department) Implements IService1.UpdateDepartment
        Using context As New STESchoolModelTypes.SchoolEntities()
            Try
                ' Perform validation on the updated order before applying the changes.

                ' The ApplyChanges method examines the change tracking information 
                ' contained in the graph of self-tracking entities to infer the set of operations
                ' that need to be performed to reflect the changes in the database. 
                context.Departments.ApplyChanges(updated)

                context.SaveChanges()
            Catch ex As UpdateException
                ' To avoid propagating exception messages that contain sensitive data to the client tier, 
                ' calls to ApplyChanges and SaveChanges should be wrapped in exception handling code.
                Throw New InvalidOperationException("Failed to update the department. Try your request again.")
            End Try
        End Using
    End Sub

    ''' <summary>
    ''' Gets all the departments and related courses. 
    ''' </summary>
    Public Function GetDepartments() As List(Of Department) Implements IService1.GetDepartments
        Using context As New STESchoolModelTypes.SchoolEntities()
            ' Use System.Data.Objects.ObjectQuery(T).Include to eagrly load the related courses.
            Return context.Departments.Include("Courses").OrderBy(Function(d) d.Name).ToList()
        End Using
    End Function

End Class
public class Service1 : IService1
{
    /// <summary>
    /// Updates department and its related courses. 
    /// </summary>
    public void UpdateDepartment(Department updated)
    {
        using (SchoolEntities context =
            new SchoolEntities())
        {
            try
            {
                // Perform validation on the updated order before applying the changes.

                // The ApplyChanges method examines the change tracking information 
                // contained in the graph of self-tracking entities to infer the set of operations
                // that need to be performed to reflect the changes in the database. 
                context.Departments.ApplyChanges(updated);
                context.SaveChanges();

            }
            catch (UpdateException ex)
            {
                // To avoid propagating exception messages that contain sensitive data to the client tier, 
                // calls to ApplyChanges and SaveChanges should be wrapped in exception handling code.
                throw new InvalidOperationException("Failed to update the department. Try your request again.");
            }
        }
    }

    /// <summary>
    /// Gets all the departments and related courses. 
    /// </summary>
    public List<Department> GetDepartments()
    {
        using (SchoolEntities context = new SchoolEntities())
        {
            // Use System.Data.Objects.ObjectQuery(T).Include to eagrly load the related courses.
            return context.Departments.Include("Courses").OrderBy(d => d.Name).ToList();
        }
    }

}

Проверка службы с помощью консольного клиентского приложения

  1. Создайте консольное приложение. Введите STESchoolModelTest в качестве имени проекта в том же решении, что и в предыдущем проекте.

  2. Добавьте ссылку на службу STEASchoolModelService. Для добавления ссылки на службу щелкните правой кнопкой мыши папку ссылок в обозревателе решений и выберите Добавить ссылку на службу.

    По умолчанию WCF создает прокси-объект, возвращающий коллекцию IEnumerable. Поскольку в службе STESchoolModelService метод GetDepartments возвращает List, следует сконфигурировать службу так, чтобы задать соответствующий тип возврата.

  3. Щелкните правой кнопкой мыши имя службы (ServiceReference1) и выберите Настроить ссылку на службу…. В диалоговом окне «Настроить ссылку на службу» выберите тип System.Collections.Generic.List из списка Тип коллекции.

  4. Добавьте ссылку на проект STESchoolModelTypes .

  5. Откройте файл app.config и добавьте в файл строку подключения. Откройте файл app.config проекта STESchoolModel и скопируйте элемент connectionStrings, чтобы добавить его в качестве дочернего элемента configuration в файле Web.config.

  6. Откройте файл, содержащий функцию main. Включите следующие пространства имен: STESchoolModelTest.ServiceReference1 и STESchoolModelTypes (где определены типы с самостоятельным отслеживанием).

  7. Вставьте следующий код в функцию main вашего проекта. Этот код содержит вызовы методов, которые мы определим на следующем шаге.

    ' Note, the service's GetDepartments method returns System.Collections.Generic.List.
    ' By default, when WCF generates a proxy the return collection types are converted to IEnumerable.
    ' The WCF service has to be configured to specify the List return type. 
    ' To specify the List collection type, open the Configure Service Reference dialog and 
    ' select the System.Collections.Generic.List type from the Collection type list. 
    
    Console.WriteLine("See the existing departments and courses.")
    DisplayDepartmentsAndCourses()
    Console.WriteLine()
    Console.WriteLine()
    
    ' Use some IDs to create
    ' new Department and Course. 
    ' The newly created objects will
    ' be then deleted.
    
    Dim departmentID As Integer = 100
    
    Dim courseID As Integer = 50
    
    AddNewDepartmentAndCourses(departmentID, courseID)
    Console.WriteLine("See existing and added.")
    DisplayDepartmentsAndCourses()
    Console.WriteLine()
    UpdateDepartmentAndCourses(departmentID, courseID)
    Console.WriteLine("See existing and updated.")
    DisplayDepartmentsAndCourses()
    Console.WriteLine()
    DeleteDepartmentAndCourses(departmentID)
    
    // Note, the service's GetDepartments method returns System.Collections.Generic.List.
    // By default, when WCF generates a proxy the return collection types are converted to IEnumerable.
    // The WCF service has to be configured to specify the List return type. 
    // To specify the List collection type, open the Configure Service Reference dialog and 
    // select the System.Collections.Generic.List type from the Collection type list. 
    
    Console.WriteLine("See the existing departments and courses.");
    DisplayDepartmentsAndCourses();
    Console.WriteLine();
    Console.WriteLine();
    
    // Use some IDs to create
    // new Department and Course. 
    // The newly created objects will
    // be then deleted.
    
    int departmentID = 100;
    
    int courseID = 50;
    
    AddNewDepartmentAndCourses(departmentID, courseID);
    Console.WriteLine("See existing and added.");
    DisplayDepartmentsAndCourses();
    Console.WriteLine();
    UpdateDepartmentAndCourses(departmentID, courseID);
    Console.WriteLine("See existing and updated.");
    DisplayDepartmentsAndCourses();
    Console.WriteLine();
    DeleteDepartmentAndCourses(departmentID);
    
  8. Добавьте в класс следующие методы. Эти методы иллюстрируют следующие действия: вывод объектов, возвращаемых службой, добавление новых объектов, изменение и удаление объектов. Дополнительные сведения см. в комментариях, содержащихся в коде.

    Private Sub DisplayDepartmentsAndCourses()
        Using service = New Service1Client()
            ' Get all the departments.
            Dim departments As List(Of Department) = service.GetDepartments()
            For Each d In departments
                Console.WriteLine("ID: {0}, Name: {1}", d.DepartmentID, d.Name)
                ' Get all the courses for each department. 
                ' The reason we are able to access
                ' the related courses is because the service eagrly loaded the related objects 
                ' (using the System.Data.Objects.ObjectQuery(T).Include method).
                For Each c In d.Courses.OfType(Of OnlineCourse)()
                    Console.WriteLine(" OnLineCourse ID: {0}, Title: {1}", c.CourseID, c.Title)
                Next
                For Each c In d.Courses.OfType(Of OnsiteCourse)()
                    Console.WriteLine(" OnSiteCourse ID: {0}, Title: {1}", c.CourseID, c.Title)
                Next
            Next
        End Using
    End Sub
    
    
    Private Sub AddNewDepartmentAndCourses(ByVal departmentID As Integer, ByVal courseID As Integer)
        Using service = New Service1Client()
    
            Dim newDepartment As New Department() _
                With {.DepartmentID = departmentID, _
                      .Budget = 13000D, _
                      .Name = "New Department", _
                      .StartDate = DateTime.Now _
                     }
    
            Dim newCourse As New OnlineCourse() _
                With {.CourseID = courseID, _
                     .DepartmentID = departmentID, _
                     .URL = "http://www.fineartschool.net/Trigonometry", _
                     .Title = "New Onsite Course", _
                     .Credits = 4 _
                     }
    
            ' Add the course to the department.
            newDepartment.Courses.Add(newCourse)
    
            ' The newly create objects are marked as added, the service will insert these into the store. 
            service.UpdateDepartment(newDepartment)
    
            ' Let’s make few more changes to the saved object. 
            ' Since the previous changes have now been persisted, call AcceptChanges to
            ' reset the ChangeTracker on the objects and mark the state as ObjectState.Unchanged.
            ' Note, AcceptChanges sets the tracking on, so you do not need to call StartTracking
            ' explicitly.
            newDepartment.AcceptChanges()
            newCourse.AcceptChanges()
    
            ' Because the change tracking is enabled
            ' the following change will set newCourse.ChangeTracker.State to ObjectState.Modified.
            newCourse.Credits = 6
    
            service.UpdateDepartment(newDepartment)
        End Using
    End Sub
    
    Private Sub UpdateDepartmentAndCourses(ByVal departmentID As Integer, ByVal courseID As Integer)
        Using service = New Service1Client()
            ' Get all the departments.
            Dim departments As List(Of Department) = service.GetDepartments()
            ' Use LINQ to Objects to query the departments collection 
            ' for the specific department object.
            Dim department As Department = departments.Single(Function(d) d.DepartmentID = departmentID)
            department.Budget = department.Budget - 1000D
    
            ' Get the specified course that belongs to the department.
            ' The reason we are able to access the related course
            ' is because the service eagrly loaded the related objects 
            ' (using the System.Data.Objects.ObjectQuery(T).Include method).
            Dim existingCourse As Course = department.Courses.[Single](Function(c) c.CourseID = courseID)
            existingCourse.Credits = 3
    
            service.UpdateDepartment(department)
        End Using
    End Sub
    
    Private Sub DeleteDepartmentAndCourses(ByVal departmentID As Integer)
        Using service = New Service1Client()
            Dim departments As List(Of Department) = service.GetDepartments()
    
            Dim department As Department = departments.Single(Function(d) d.DepartmentID = departmentID)
    
            ' When MarkAsDeleted is called, the entity is removed from the collection,
            ' if we modify the collection over which foreach is looping an exception will be thrown.
            ' That is why we need to make a copy of the courses collection by 
            ' calling department.Courses.ToList();
            Dim courses As List(Of Course) = department.Courses.ToList()
            For Each c In courses
    
                ' Marks each comment for the post as Deleted.
                ' If another entity have a foreign key relationship with this Course object
                ' an exception will be thrown during save operation. 
                c.MarkAsDeleted()
            Next
    
            department.MarkAsDeleted()
            service.UpdateDepartment(department)
        End Using
    End Sub
    
    static void DisplayDepartmentsAndCourses()
    {
        using (var service = new Service1Client())
        {
            // Get all the departments.
            List<Department> departments = service.GetDepartments();
            foreach (var d in departments)
            {
                Console.WriteLine("ID: {0}, Name: {1}", d.DepartmentID, d.Name);
                // Get all the courses for each department. 
                // The reason we are able to access
                // the related courses is because the service eagrly loaded the related objects 
                // (using the System.Data.Objects.ObjectQuery(T).Include method).
                foreach (var c in d.Courses.OfType<OnlineCourse>())
                {
                    Console.WriteLine("  OnLineCourse ID: {0}, Title: {1}", c.CourseID, c.Title);
                }
                foreach (var c in d.Courses.OfType<OnsiteCourse>())
                {
                    Console.WriteLine("  OnSiteCourse ID: {0}, Title: {1}", c.CourseID, c.Title);
                }
            }
        }
    }
    
    
    static void AddNewDepartmentAndCourses(int departmentID, int courseID)
    {
        using (var service = new Service1Client())
        {
            Department newDepartment = new Department()
            {
                DepartmentID = departmentID,
                Budget = 13000.000m,
                Name = "New Department",
                StartDate = DateTime.Now
            };
    
            OnlineCourse newCourse = new OnlineCourse()
            { 
                CourseID = courseID,
                DepartmentID = departmentID,
                URL = "http://www.fineartschool.net/Trigonometry",
                Title = "New Onsite Course",
                Credits = 4
            };
    
            // Add the course to the department.
            newDepartment.Courses.Add(newCourse);
    
            // The newly create objects are marked as added, the service will insert these into the store. 
            service.UpdateDepartment(newDepartment);
    
            // Let’s make few more changes to the saved object. 
            // Since the previous changes have now been persisted, call AcceptChanges to
            // reset the ChangeTracker on the objects and mark the state as ObjectState.Unchanged.
            // Note, AcceptChanges sets the tracking on, so you do not need to call StartTracking
            // explicitly.
            newDepartment.AcceptChanges();
            newCourse.AcceptChanges();
    
            // Because the change tracking is enabled
            // the following change will set newCourse.ChangeTracker.State to ObjectState.Modified.
            newCourse.Credits = 6;
            service.UpdateDepartment(newDepartment);
    
        }
    }
    
    static void UpdateDepartmentAndCourses(int departmentID, int courseID)
    {
        using (var service = new Service1Client())
        {
            // Get all the departments.
            List<Department> departments = service.GetDepartments();
            // Use LINQ to Objects to query the departments collection 
            // for the specific department object.
            Department department = departments.Single(d => d.DepartmentID == departmentID);
            department.Budget = department.Budget - 1000.00m;
    
            // Get the specified course that belongs to the department.
            // The reason we are able to access the related course
            // is because the service eagrly loaded the related objects 
            // (using the System.Data.Objects.ObjectQuery(T).Include method).
            Course existingCourse = department.Courses.Single(c => c.CourseID == courseID);
            existingCourse.Credits = 3;
    
            service.UpdateDepartment(department);
        }
    }
    
    static void DeleteDepartmentAndCourses(int departmentID)
    {
        using (var service = new Service1Client())
        {
            List<Department> departments = service.GetDepartments();
    
            Department department = departments.Single(d => d.DepartmentID == departmentID);
    
            // When MarkAsDeleted is called, the entity is removed from the collection,
            // if we modify the collection over which foreach is looping an exception will be thrown.
            // That is why we need to make a copy of the courses collection by 
            // calling department.Courses.ToList();
            List<Course> courses = department.Courses.ToList();
            foreach (var c in courses)
            {
    
                // Marks each comment for the post as Deleted.
                // If another entity have a foreign key relationship with this Course object
                // an exception will be thrown during save operation. 
                c.MarkAsDeleted();
            }
    
            department.MarkAsDeleted();
            service.UpdateDepartment(department);
        }
    }
    

Проверка службы с помощью консольного клиентского приложения WPF

  1. Создайте приложение WPF. Введите STESchoolModelWPFTest в качестве имени проекта в том же решении, где находится предыдущий проект.

  2. Добавьте ссылку на службу STEASchoolModelService. Для добавления ссылки на службу щелкните правой кнопкой мыши папку ссылок в обозревателе решений и выберите Добавить ссылку на службу.

    По умолчанию WCF создает прокси-объект, возвращающий коллекцию IEnumerable. Поскольку в службе STESchoolModelService метод GetDepartments возвращает List, следует сконфигурировать службу так, чтобы задать соответствующий тип возврата.

  3. Щелкните правой кнопкой мыши имя службы (ServiceReference1) и выберите Настроить ссылку на службу…. В диалоговом окне «Настроить ссылку на службу» выберите тип System.Collections.Generic.List из списка Тип коллекции.

  4. Добавьте ссылку на проект STESchoolModelTypes .

  5. Откройте файл app.config и добавьте в файл строку подключения. Откройте файл app.config проекта STESchoolModel и скопируйте элемент connectionStrings, чтобы добавить его в качестве дочернего элемента configuration в файле Web.config.

    Теперь можно удалить файл app.config проекта STESchoolModel , потому что он не используется.

    По умолчанию шаблон проекта добавляет к проекту файл MainWindow.xaml и соответствующий файл фонового кода.

  6. Откройте файл MainWindow.xaml и замените код XAML, созданный по умолчанию, кодом XAML, задающим окно STESchoolModelWPFTest в WPF. Дополнительные сведения см. в комментариях, содержащихся в коде.

    <Window x:Class="STESchoolModelWPFTest.MainWindow"
            xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="508" Width="919" Loaded="Window_Loaded">
        <!-- The code begind code sets the departmentsItemsGrid to the root of the object graph.-->
        <Grid Name="departmentsItemsGrid">
            <!-- comboBoxDepartment points to the root of the graph, that is why the Path is not specified-->
            <ComboBox DisplayMemberPath="DepartmentID" ItemsSource="{Binding}"
                      IsSynchronizedWithCurrentItem="true" 
                      Height="23" Margin="122,12,198,0" Name="comboBoxDepartment" VerticalAlignment="Top"/>
            <!-- listViewItems Path is set to Courses because it is bound to Department.Courses.-->
            <ListView ItemsSource="{Binding Path=Courses}" Name="listViewItems" Margin="34,46,34,50" >
                <ListView.View>
                    <GridView AllowsColumnReorder="False" ColumnHeaderToolTip="Courses" >
                        <GridViewColumn DisplayMemberBinding="{Binding Path=CourseID}" 
                            Header="CourseID" Width="70"/>
                        <!--The TextBox controls are embedded in the two of the following columns.
                            This is done to enable editing in the ListView control. -->
                        <GridViewColumn Header="Title" Width="100">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBox Height="25" Width="100" Text="{Binding Path=Title}" />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="Credits" Width="100" >
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBox Height="25" Width="100" Text="{Binding Path=Credits}" />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView>
                </ListView.View>
            </ListView>
            <Label Height="28" Margin="34,12,0,0" Name="departmentLabel" VerticalAlignment="Top" 
                   HorizontalAlignment="Left" Width="93">Department:</Label>
            <!--When the Save and Close button is clicked all the objects will be sent to the service 
                where all the updated objects will be saved to the database. -->
            <Button Height="23" HorizontalAlignment="Right" Margin="0,0,34,12" 
                    Name="buttonClose" VerticalAlignment="Bottom" Width="127" Click="buttonClose_Click">Save and Close</Button>
        </Grid>
    </Window>
    
  7. Откройте файл MainWindow.xaml.cs (или .vb) и замените код, созданный по умолчанию, следующим кодом (дополнительные объяснения см. в комментариях, содержащихся в коде).

    Class MainWindow
        Dim departments As List(Of Department)
    
        Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
            Using service = New Service1Client()
                ' Set the parent of of your data bound controls to the root of the graph.
                ' In the xaml page the appropriate paths should be set on each data bound control.
                ' For the comboBoxDepartment it is empty because it is bound to Departments (which is root).
                ' For the listViewItems it is set to Courses because it is bound to Department.Courses.
                ' Note, that the TextBox controls are embedded in the two of the columns in the listViewItems.
                ' This is done to enable editing in the ListView control.
                departments = service.GetDepartments()
                Me.departmentsItemsGrid.DataContext = departments
            End Using
        End Sub
    
        Private Sub buttonSave_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles buttonSave.Click
            Using service = New Service1Client()
                ' Save all the departments and their courses. 
                For Each department In departments
                    service.UpdateDepartment(department)
    
                    ' Call AcceptChanges on all the objects 
                    ' to resets the change tracker and set the state of the objects to Unchanged.
                    department.AcceptChanges()
                    For Each course In department.Courses
                        course.AcceptChanges()
                    Next
                Next
            End Using
        End Sub
    
        Private Sub buttonClose_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles buttonClose.Click
            ' Close the form.
            Me.Close()
        End Sub
    End Class
    
    public partial class MainWindow : Window
    {
        private List<Department> departments;
    
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            using (var service = new Service1Client())
            {
                // Set the parent of of your data bound controls to the root of the graph.
                // In the xaml page the appropriate paths should be set on each data bound control.
                // For the comboBoxDepartment it is empty because it is bound to Departments (which is root).
                // For the listViewItems it is set to Courses because it is bound to Department.Courses.
                // Note, that the TextBox controls are embedded in the two of the columns in the listViewItems.
                // This is done to enable editing in the ListView control.
                departments = service.GetDepartments();
                this.departmentsItemsGrid.DataContext = departments;
            }
        }
    
        private void buttonSave_Click(object sender, RoutedEventArgs e)
        {
            using (var service = new Service1Client())
            {
                // Save all the departments and their courses. 
                foreach (var department in departments)
                {
                    service.UpdateDepartment(department);
    
                    // Call AcceptChanges on all the objects 
                    // to resets the change tracker and set the state of the objects to Unchanged.
                    department.AcceptChanges();
                    foreach (var course in department.Courses)
                        course.AcceptChanges();
                }
            }
    
        }
    
        private void buttonClose_Click(object sender, RoutedEventArgs e)
        {
            //Close the form.
            this.Close();
        }
    }
    

См. также

Основные понятия

Работа с сущностями с самостоятельным отслеживанием
Сериализация объектов (платформа Entity Framework)
Построение многоуровневых приложений (платформа Entity Framework)

Другие ресурсы

ADO.NET Self-Tracking Entity Template