연습: 자동 추적 엔터티 serialize(Entity Framework)

이 항목의 연습에서는 WCF(Windows Communication Foundation) 서비스에서 엔터티 그래프를 반환하는 일련의 작업을 노출하는 시나리오를 보여 줍니다. 클라이언트 응용 프로그램에서는 해당 그래프를 조작하고 Entity Framework를 사용하여 업데이트의 유효성을 검사하고 데이터베이스에 저장하는 서비스 작업에 수정 내용을 전송합니다. 자세한 내용은 자동 추적 엔터티 작업을 참조하십시오.

일반적으로 자동 추적 엔터티 형식이 포함된 프로젝트에서 모델 프로젝트를 분리하려고 합니다. 이렇게 하면 클라이언트에서 엔터티 형식 프로젝트를 포함하기만 하면 됩니다.

이러한 격리 환경을 구현하는 한 가지 방법은 자동 추적 엔터티 형식을 모델에서 분리하고 해당 엔터티 형식을 생성하는 템플릿을 별도의 클래스 라이브러리로 이동하는 것입니다. .edmx 파일의 위치는 메타데이터에 액세스하기 위해 자동 추적 엔터티 템플릿에 포함되는 데 필요합니다. 따라서 템플릿을 원래 프로젝트에서 다른 프로젝트로 이동할 경우 템플릿 파일을 편집기에서 열고 inputFile 문자열을 .edmx 파일의 상대 위치로 수정해야 합니다. 템플릿으로 작업하는 방법은 ADO.NET Self-Tracking Entity Template을 참조하십시오.

엔터티 형식을 모델에서 분리하는 다른 방법은 템플릿 파일을 원래 프로젝트에 두되 템플릿의 코드 생성 기능을 사용하지 않도록 설정하는 것입니다. 그런 다음 원래 프로젝트 대신 다른 프로젝트에 코드가 생성되도록 다른 프로젝트에서 템플릿에 연결합니다. 항목을 프로젝트에 링크로 추가하면 해당 항목의 실제 내용은 원래 프로젝트가 나타내는 위치에 유지됩니다. 이 연습에서는 이와 같이 엔터티 형식을 모델에서 분리하는 방법을 보여 줍니다.

이 연습에서는 다음 작업을 수행합니다.

  • School 기반 모델이 포함된 클래스 라이브러리 프로젝트를 만듭니다.

  • ADO.NET 자동 추적 엔터티 생성기 템플릿을 사용하여 엔터티 형식, 형식화된 ObjectContext 및 오버로드된 ApplyChanges 메서드가 포함된 확장 클래스를 생성합니다.

  • 첫 번째 프로젝트에서 만든 자동 추적 엔터티 형식 템플릿에 연결되는 클래스 라이브러리 프로젝트를 만듭니다.

  • Entity Framework 를 사용하여 엔터티 그래프를 반환하는 작업 집합을 노출하고 클라이언트에서 수행한 변경 내용을 데이터베이스에 적용하는 WCF 서비스를 만듭니다.

  • WCF(Windows Presentation Foundation) 서비스에 노출된 작업을 사용하여 그래프를 조작하고 수정 내용을 전송하는 클라이언트 응용 프로그램(콘솔 및 WPF)을 만듭니다.

Ee789839.note(ko-kr,VS.100).gif참고:
STESchoolModelExample은 MSDN Code Gallery(MSDN 코드 갤러리)의 Entity Framework Documentation Samples 사이트에서 다운로드할 수 있습니다.

자동 추적 엔터티와 개체 컨텍스트 클래스를 포함하는 클래스 라이브러리 프로젝트를 만들려면

  1. 새 클래스 라이브러리 프로젝트를 만듭니다. 프로젝트 및 솔루션 이름으로 STESchoolModel을 입력합니다.

  2. 프로젝트에 추가된 기본 소스 코드 파일을 제거합니다.

  3. 엔터티 데이터 모델 마법사를 사용하여 School 데이터베이스의 Department, Course, OnlineCourseOnsiteCourse 테이블을 기반으로 하는 모델을 생성합니다. 자세한 내용은 School 모델을 참조하십시오.

  4. ADO.NET Entity Data Model Designer(Entity Designer)에서 .edmx 파일을 엽니다.

  5. Walkthrough: Mapping Inheritance - Table-per-Type 항목의 상속 매핑 지침을 따릅니다.

  6. Entity Designer 화면의 빈 영역을 마우스 오른쪽 단추로 클릭하고 코드 생성 항목 추가를 가리킨 다음 ADO.NET 자동 추적 엔터티 생성기를 선택합니다. 기본 템플릿 이름을 SchoolModel로 변경합니다.

    Ee789839.note(ko-kr,VS.100).gif참고:
    프로젝트에 템플릿 파일을 추가하면 템플릿의 소스를 신뢰할 수 있는 경우에만 허용할지 묻는 보안 경고 메시지가 나타날 수 있습니다.적용을 클릭합니다.

  7. SchoolModel.Context.ttSchoolModel.tt 폴더가 프로젝트에 추가됩니다. SchoolModel.Context.tt 폴더에는 형식화된 ObjectContext와 오버로드된 ApplyChanges 메서드가 포함된 확장 클래스를 정의하는 두 개의 파일이 있습니다. SchoolModel.tt 폴더에는 엔터티 형식과 자동 추적 엔터티에 사용되는 변경 내용 추적 논리가 포함된 도우미 클래스를 정의하는 파일이 있습니다.

    다음 두 단계에서는 이 프로젝트에서 코드 생성을 사용하지 않도록 설정합니다. 코드 생성은 나중에 STESchoolModelTypes 클래스 라이브러리의 형식과 STESchoolModelService의 개체 컨텍스트에 대해 사용하도록 설정됩니다.

  8. SchoolModel.tt를 선택합니다. 속성 창의 CustomTool 속성에서 TextTemplatingFileGenerator를 지웁니다. SchoolModel.tt 폴더의 파일을 삭제합니다.

  9. SchoolModel.Context.tt를 선택합니다. 속성 창에서 CustomTool 속성의 값을 지웁니다. 그런 다음 SchoolModel.Context.tt 폴더의 파일을 삭제합니다.

    Visual Basic 프로젝트로 작업하는 경우 프로젝트의 모든 파일을 보려면 솔루션 탐색기에서 모든 파일 표시를 클릭해야 할 수 있습니다.

  10. 프로젝트를 컴파일합니다.

자동 추적 형식 템플릿에 연결되는 클래스 라이브러리 프로젝트를 만들려면

  1. 이전 프로젝트와 동일한 솔루션에 STESchoolModelTypes라는 새 클래스 라이브러리 프로젝트를 만듭니다.

  2. 프로젝트에 추가된 기본 소스 코드 파일을 제거합니다.

  3. 자동 추적 엔터티 형식이 이 솔루션에 생성되도록 SchoolModel.tt 파일에 대한 링크를 추가합니다. 솔루션 탐색기에서 STESchoolModelTypes를 마우스 오른쪽 단추로 클릭하고 추가를 클릭한 다음 기존 항목을 클릭합니다.

  4. 기존 항목 추가 대화 상자에서 STESchoolModel 프로젝트로 이동한 다음 SchoolModel.tt를 클릭합니다. Enter 키는 누르지 마십시오. 추가 목록에서 링크로 추가를 선택합니다.

  5. System.Runtime.Serialization 라이브러리에 대한 참조를 추가합니다. 이 라이브러리는 serialize할 수 있는 엔터티 형식에서 사용되는 WCF DataContractDataMember 특성에 필요합니다.

  6. 프로젝트를 컴파일합니다.

WCF 서비스 응용 프로그램 프로젝트를 만들고 구성하려면

  1. 이전 프로젝트와 동일한 솔루션에 STESchoolModelService라는 WCF 서비스 응용 프로그램 프로젝트를 만듭니다.

  2. System.Data.Entity.dll에 대한 참조를 추가합니다.

  3. STESchoolModelSTESchoolModelTypes 프로젝트에 대한 참조를 추가합니다.

  4. 컨텍스트 형식이 이 솔루션에 생성되도록 SchoolModel.Context.tt 파일에 대한 링크를 추가합니다. 솔루션 탐색기에서 STESchoolModelService를 마우스 오른쪽 단추로 클릭하고 추가를 클릭한 다음 기존 항목을 클릭합니다.

  5. 기존 항목 추가 대화 상자에서 STESchoolModel 프로젝트로 이동한 다음 SchoolModel.Context.tt를 클릭합니다. Enter 키는 누르지 마십시오. 추가 목록에서 링크로 추가를 선택합니다.

  6. SchoolModel.Context.tt 파일의 속성 창에서 사용자 지정 도구 네임스페이스 속성에 STESchoolModelTypes를 입력합니다. 이렇게 하면 자동 추적 엔터티 형식의 네임스페이스와 동일한 네임스페이스에 개체 컨텍스트 형식이 추가됩니다. 이 작업은 필수입니다.

  7. (Visual Basic만 해당) Import STESchoolModelTypesSchoolModel.Context.tt 파일에서 생성된 소스 파일에 추가합니다. SchoolModel.Context.tt 파일을 열고 Imports System 문자열을 찾습니다. 다른 Import 뒤에 Import STESchoolModelTypes를 추가합니다. 생성되는 소스 파일에 이 네임스페이스가 포함됩니다.

  8. Entity Framework 런타임에서 메타데이터를 찾을 수 있도록 Web.config 파일에 연결 문자열을 추가합니다. STESchoolModel 프로젝트의 app.config 파일을 엽니다. connectionStrings 요소를 복사한 다음 Web.config 파일의 configuration 요소에 자식 요소로 추가합니다.

  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.STESchoolModelTypesService1.srv.cs 파일에 추가합니다.

  15. 서비스 클래스 정의를 다음 코드로 바꿉니다.

Ee789839.Important(ko-kr,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 컬렉션을 반환하는 프록시를 생성합니다. STESchoolModelServiceGetDepartments 메서드가 List를 반환하기 때문에 서비스를 구성하여 적절한 반환 형식을 지정해야 합니다.

  3. 서비스 이름(ServiceReference1)을 마우스 오른쪽 단추로 클릭하고 **서비스 참조 구성…**을 선택합니다. 서비스 참조 구성 대화 상자의 컬렉션 형식 목록에서 System.Collections.Generic.List 형식을 선택합니다.

  4. STESchoolModelTypes 프로젝트에 대한 참조를 추가합니다.

  5. app.config 파일을 열고 연결 문자열을 파일에 추가합니다. STESchoolModel 프로젝트의 app.config 파일을 열고 connectionStrings 요소를 복사하여 Web.config 파일의 configuration 요소에 자식 요소로 추가합니다.

  6. main 함수가 포함된 파일을 엽니다. STESchoolModelTest.ServiceReference1STESchoolModelTypes 네임스페이스(자동 추적 형식이 정의된 경우)를 포함합니다.

  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 컬렉션을 반환하는 프록시를 생성합니다. STESchoolModelServiceGetDepartments 메서드가 List를 반환하기 때문에 서비스를 구성하여 적절한 반환 형식을 지정해야 합니다.

  3. 서비스 이름(ServiceReference1)을 마우스 오른쪽 단추로 클릭하고 **서비스 참조 구성…**을 선택합니다. 서비스 참조 구성 대화 상자의 컬렉션 형식 목록에서 System.Collections.Generic.List 형식을 선택합니다.

  4. STESchoolModelTypes 프로젝트에 대한 참조를 추가합니다.

  5. app.config 파일을 열고 연결 문자열을 파일에 추가합니다. STESchoolModel 프로젝트의 app.config 파일을 열고 connectionStrings 요소를 복사하여 Web.config 파일의 configuration 요소에 자식 요소로 추가합니다.

    STESchoolModel 프로젝트의 app.config 파일이 사용되지 않았기 때문에 이제 해당 파일을 삭제할 수 있습니다.

    기본적으로 프로젝트 템플릿에서는 MainWindow.xaml 파일과 해당 코드 숨김 파일을 프로젝트에 추가합니다.

  6. MainWindow.xaml을 연 다음 기본 XAML 코드를 WPF에서 STESchoolModelWPFTest 창을 정의하는 XAML로 바꿉니다. 자세한 내용은 코드 주석을 참조하십시오.

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

참고 항목

개념

자동 추적 엔터티 작업
개체 Serialize(Entity Framework)
n 계층 응용 프로그램 작성(Entity Framework)

기타 리소스

ADO.NET Self-Tracking Entity Template