다음을 통해 공유


Entity Framework 4.0 및 ObjectDataSource 컨트롤 사용, 2부: 비즈니스 논리 계층 및 단위 테스트 추가

작성자 : Tom Dykstra

이 자습서 시리즈는 Entity Framework 4.0 자습서 시리즈를 사용하여 시작 만든 Contoso University 웹 애플리케이션을 기반으로 합니다. 이전 자습서를 완료하지 않은 경우 이 자습서의 시작점으로 만든 애플리케이션을 다운로드 할 수 있습니다. 전체 자습서 시리즈에서 만든 애플리케이션을 다운로드 할 수도 있습니다. 자습서에 대한 질문이 있는 경우 ASP.NET Entity Framework 포럼에 게시할 수 있습니다.

이전 자습서에서는 Entity Framework 및 컨트롤을 사용하여 n 계층 웹 애플리케이션을 ObjectDataSource 만들었습니다. 이 자습서에서는 BLL(비즈니스 논리 계층)과 DAL(데이터 액세스 계층)을 별도로 유지하면서 비즈니스 논리를 추가하는 방법을 보여 주며 BLL에 대한 자동화된 단위 테스트를 만드는 방법을 보여 줍니다.

이 자습서에서는 다음 작업을 완료합니다.

  • 필요한 데이터 액세스 메서드를 선언하는 리포지토리 인터페이스를 만듭니다.
  • 리포지토리 클래스에서 리포지토리 인터페이스를 구현합니다.
  • 리포지토리 클래스를 호출하여 데이터 액세스 함수를 수행하는 비즈니스 논리 클래스를 만듭니다.
  • ObjectDataSource 리포지토리 클래스 대신 비즈니스 논리 클래스에 컨트롤을 연결합니다.
  • 데이터 저장소에 메모리 내 컬렉션을 사용하는 단위 테스트 프로젝트 및 리포지토리 클래스를 만듭니다.
  • 비즈니스 논리 클래스에 추가하려는 비즈니스 논리에 대한 단위 테스트를 만든 다음 테스트를 실행하고 실패를 확인합니다.
  • 비즈니스 논리 클래스에서 비즈니스 논리를 구현한 다음 단위 테스트를 다시 실행하고 통과를 확인합니다.

이전 자습서에서 만든 Departments.aspxDepartmentsAdd.aspx 페이지를 사용합니다.

리포지토리 인터페이스 만들기

먼저 리포지토리 인터페이스를 만듭니다.

Image08

DAL 폴더에서 새 클래스 파일을 만들고 이름을 ISchoolRepository.cs로 지정하고 기존 코드를 다음 코드로 바꿉니다.

using System;
using System.Collections.Generic;

namespace ContosoUniversity.DAL
{
    public interface ISchoolRepository : IDisposable
    {
        IEnumerable<Department> GetDepartments();
        void InsertDepartment(Department department);
        void DeleteDepartment(Department department);
        void UpdateDepartment(Department department, Department origDepartment);
        IEnumerable<InstructorName> GetInstructorNames();
    }
}

인터페이스는 리포지토리 클래스에서 만든 각 CRUD(만들기, 읽기, 업데이트, 삭제) 메서드에 대해 하나의 메서드를 정의합니다.

SchoolRepository.csSchoolRepository 클래스에서 이 클래스가 인터페이스를 ISchoolRepository 구현함을 나타냅니다.

public class SchoolRepository : IDisposable, ISchoolRepository

Business-Logic 클래스 만들기

다음으로, 비즈니스 논리 클래스를 만듭니다. 이렇게 하면 컨트롤에서 실행할 비즈니스 논리를 ObjectDataSource 추가할 수 있지만 아직 추가하지는 않습니다. 지금은 새 비즈니스 논리 클래스가 리포지토리와 동일한 CRUD 작업만 수행합니다.

Image09

새 폴더를 만들고 이름을 BLL로 지정합니다. (실제 애플리케이션에서 비즈니스 논리 계층은 일반적으로 클래스 라이브러리( 별도의 프로젝트)로 구현되지만 이 자습서를 간단하게 유지하기 위해 BLL 클래스는 프로젝트 폴더에 유지됩니다.

BLL 폴더에서 새 클래스 파일을 만들고 이름을 SchoolBL.cs로 지정하고 기존 코드를 다음 코드로 바꿉니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using ContosoUniversity.DAL;

namespace ContosoUniversity.BLL
{
    public class SchoolBL : IDisposable
    {
        private ISchoolRepository schoolRepository;

        public SchoolBL()
        {
            this.schoolRepository = new SchoolRepository();
        }

        public SchoolBL(ISchoolRepository schoolRepository)
        {
            this.schoolRepository = schoolRepository;
        }

        public IEnumerable<Department> GetDepartments()
        {
            return schoolRepository.GetDepartments();
        }

        public void InsertDepartment(Department department)
        {
            try
            {
                schoolRepository.InsertDepartment(department);
            }
            catch (Exception ex)
            {
                //Include catch blocks for specific exceptions first,
                //and handle or log the error as appropriate in each.
                //Include a generic catch block like this one last.
                throw ex;
            }
        }

        public void DeleteDepartment(Department department)
        {
            try
            {
                schoolRepository.DeleteDepartment(department);
            }
            catch (Exception ex)
            {
                //Include catch blocks for specific exceptions first,
                //and handle or log the error as appropriate in each.
                //Include a generic catch block like this one last.
                throw ex;
            }
        }

        public void UpdateDepartment(Department department, Department origDepartment)
        {
            try
            {
                schoolRepository.UpdateDepartment(department, origDepartment);
            }
            catch (Exception ex)
            {
                //Include catch blocks for specific exceptions first,
                //and handle or log the error as appropriate in each.
                //Include a generic catch block like this one last.
                throw ex;
            }

        }

        public IEnumerable<InstructorName> GetInstructorNames()
        {
            return schoolRepository.GetInstructorNames();
        }

        private bool disposedValue = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposedValue)
            {
                if (disposing)
                {
                    schoolRepository.Dispose();
                }
            }
            this.disposedValue = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

    }
}

이 코드는 이전에 리포지토리 클래스에서 본 것과 동일한 CRUD 메서드를 만들지만 Entity Framework 메서드에 직접 액세스하는 대신 리포지토리 클래스 메서드를 호출합니다.

리포지토리 클래스에 대한 참조를 보유하는 클래스 변수는 인터페이스 형식으로 정의되며 리포지토리 클래스를 인스턴스화하는 코드는 두 생성자에 포함됩니다. 매개 변수가 없는 생성자는 컨트롤에서 ObjectDataSource 사용됩니다. 앞에서 만든 클래스의 SchoolRepository instance 만듭니다. 다른 생성자를 사용하면 비즈니스 논리 클래스를 인스턴스화하는 모든 코드가 리포지토리 인터페이스를 구현하는 모든 개체를 전달할 수 있습니다.

리포지토리 클래스와 두 생성자를 호출하는 CRUD 메서드를 사용하면 선택한 백 엔드 데이터 저장소와 함께 비즈니스 논리 클래스를 사용할 수 있습니다. 비즈니스 논리 클래스는 호출하는 클래스가 데이터를 유지하는 방법을 인식할 필요가 없습니다. (이를 지속성 무지라고도 합니다.) 이렇게 하면 비즈니스 논리 클래스를 메모리 List 내 컬렉션처럼 간단한 항목을 사용하여 데이터를 저장하는 리포지토리 구현에 연결할 수 있으므로 단위 테스트가 용이합니다.

참고

기술적으로 엔터티 개체는 Entity Framework의 클래스에서 상속되는 클래스에서 인스턴스화되므로 지속성 무지가 EntityObject 아닙니다. 완전한 지속성 무시를 위해 클래스에서 상속되는 개체 대신 일반 이전 CLR 개체 또는 POCOEntityObject 사용할 수 있습니다. POTO 사용은 이 자습서의 scope 넘어가고 있습니다. 자세한 내용은 MSDN 웹 사이트의 테스트 가능성 및 Entity Framework 4.0 을 참조하세요.)

이제 리포지토리 대신 비즈니스 논리 클래스에 컨트롤을 연결 ObjectDataSource 하고 모든 것이 이전과 같이 작동하는지 확인할 수 있습니다.

Departments.aspxDepartmentsAdd.aspx에서 각 TypeName="ContosoUniversity.DAL.SchoolRepository" 항목을 "로 TypeName="ContosoUniversity.BLL.SchoolBL변경합니다. (모두 4개의 인스턴스가 있습니다.)

Departments.aspxDepartmentsAdd.aspx 페이지를 실행하여 이전과 마찬가지로 여전히 작동하는지 확인합니다.

Image01

Image02

Unit-Test 프로젝트 및 리포지토리 구현 만들기

테스트 프로젝트 템플릿을 사용하여 솔루션에 새 프로젝트를 추가하고 이름을 로 지정합니다 ContosoUniversity.Tests.

테스트 프로젝트에서 에 대한 참조 System.Data.Entity 를 추가하고 프로젝트에 프로젝트 참조를 추가합니다 ContosoUniversity .

이제 단위 테스트에 사용할 리포지토리 클래스를 만들 수 있습니다. 이 리포지토리의 데이터 저장소는 클래스 내에 있습니다.

Image12

테스트 프로젝트에서 새 클래스 파일을 만들고 이름을 MockSchoolRepository.cs로 지정하고 기존 코드를 다음 코드로 바꿉니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ContosoUniversity.DAL;
using ContosoUniversity.BLL;

namespace ContosoUniversity.Tests
{
    class MockSchoolRepository : ISchoolRepository, IDisposable
    {
        List<Department> departments = new List<Department>();
        List<InstructorName> instructors = new List<InstructorName>();

        public IEnumerable<Department> GetDepartments()
        {
            return departments;
        }

        public void InsertDepartment(Department department)
        {
            departments.Add(department);
        }

        public void DeleteDepartment(Department department)
        {
            departments.Remove(department);
        }

        public void UpdateDepartment(Department department, Department origDepartment)
        {
            departments.Remove(origDepartment);
            departments.Add(department);
        }

        public IEnumerable<InstructorName> GetInstructorNames()
        {
            return instructors;
        }

        public void Dispose()
        {
            
        }
    }
}

이 리포지토리 클래스에는 Entity Framework에 직접 액세스하는 것과 동일한 CRUD 메서드가 있지만 데이터베이스 대신 메모리의 컬렉션에서 작동 List 합니다. 이렇게 하면 테스트 클래스가 비즈니스 논리 클래스에 대한 단위 테스트를 쉽게 설정하고 유효성을 검사할 수 있습니다.

단위 테스트 만들기

테스트 프로젝트 템플릿은 스텁 단위 테스트 클래스를 만들었으며, 다음 작업은 비즈니스 논리 클래스에 추가하려는 비즈니스 논리에 대한 단위 테스트 메서드를 추가하여 이 클래스를 수정하는 것입니다.

이미지13

Contoso University에서 모든 개별 강사는 단일 부서의 관리자일 수 있으며 이 규칙을 적용하려면 비즈니스 논리를 추가해야 합니다. 먼저 테스트를 추가하고 테스트를 실행하여 실패를 확인합니다. 그런 다음, 코드를 추가하고 테스트를 다시 실행합니다. 테스트를 통과해야 합니다.

UnitTest1.cs 파일을 열고 ContosoUniversity 프로젝트에서 만든 비즈니스 논리 및 데이터 액세스 계층에 대한 문을 추가 using 합니다.

using ContosoUniversity.BLL;
using ContosoUniversity.DAL;

메서드를 TestMethod1 다음 메서드로 바꿉 있습니다.

private SchoolBL CreateSchoolBL()
{
    var schoolRepository = new MockSchoolRepository();
    var schoolBL = new SchoolBL(schoolRepository);
    schoolBL.InsertDepartment(new Department() { Name = "First Department", DepartmentID = 0, Administrator = 1, Person = new Instructor () { FirstMidName = "Admin", LastName = "One" } });
    schoolBL.InsertDepartment(new Department() { Name = "Second Department", DepartmentID = 1, Administrator = 2, Person = new Instructor() { FirstMidName = "Admin", LastName = "Two" } });
    schoolBL.InsertDepartment(new Department() { Name = "Third Department", DepartmentID = 2, Administrator = 3, Person = new Instructor() { FirstMidName = "Admin", LastName = "Three" } });
    return schoolBL;
}

[TestMethod]
[ExpectedException(typeof(DuplicateAdministratorException))]
public void AdministratorAssignmentRestrictionOnInsert()
{
    var schoolBL = CreateSchoolBL();
    schoolBL.InsertDepartment(new Department() { Name = "Fourth Department", DepartmentID = 3, Administrator = 2, Person = new Instructor() { FirstMidName = "Admin", LastName = "Two" } });
}

[TestMethod]
[ExpectedException(typeof(DuplicateAdministratorException))]
public void AdministratorAssignmentRestrictionOnUpdate()
{
    var schoolBL = CreateSchoolBL();
    var origDepartment = (from d in schoolBL.GetDepartments()
                          where d.Name == "Second Department"
                          select d).First();
    var department = (from d in schoolBL.GetDepartments()
                          where d.Name == "Second Department"
                          select d).First();
    department.Administrator = 1;
    schoolBL.UpdateDepartment(department, origDepartment);
}

메서드는 CreateSchoolBL 단위 테스트 프로젝트에 대해 만든 리포지토리 클래스의 instance 만든 다음 비즈니스 논리 클래스의 새 instance 전달합니다. 그런 다음, 메서드는 비즈니스 논리 클래스를 사용하여 테스트 메서드에 사용할 수 있는 세 개의 부서를 삽입합니다.

테스트 메서드는 누군가가 기존 부서와 동일한 관리자가 있는 새 부서를 삽입하려고 하거나 다른 부서의 관리자인 사람의 ID로 설정하여 부서의 관리자를 업데이트하려고 하는 경우 비즈니스 논리 클래스에서 예외를 throw하는지 확인합니다.

아직 예외 클래스를 만들지 않았으므로 이 코드는 컴파일되지 않습니다. 컴파일하려면 마우스 오른쪽 단추를 클릭하고 DuplicateAdministratorException생성을 선택한 다음 클래스를 선택합니다.

클래스 하위 메뉴에서 선택한 생성을 보여 주는 스크린샷.

그러면 기본 프로젝트에서 예외 클래스를 만든 후 삭제할 수 있는 클래스가 테스트 프로젝트에 만들어집니다. 및 는 비즈니스 논리를 구현했습니다.

테스트 프로젝트를 실행합니다. 예상대로 테스트가 실패합니다.

Image03

테스트 통과를 위한 비즈니스 논리 추가

다음으로, 이미 다른 부서의 관리자인 부서의 관리자로 설정할 수 없는 비즈니스 논리를 구현합니다. 비즈니스 논리 계층에서 예외를 throw한 다음 사용자가 부서를 편집하고 이미 관리자인 사람을 선택한 후 업데이트를 클릭하면 프레젠테이션 계층에서 예외를 throw합니다. (페이지를 렌더링하기 전에 이미 관리자인 강사를 드롭다운 목록에서 제거할 수도 있지만 여기서는 비즈니스 논리 계층으로 작업하는 것이 목적입니다.)

먼저 사용자가 강사를 둘 이상의 부서의 관리자로 만들려고 할 때 throw할 예외 클래스를 만듭니다. 기본 프로젝트에서 BLL 폴더에 새 클래스 파일을 만들고, 이름을 DuplicateAdministratorException.cs로 지정하고, 기존 코드를 다음 코드로 바꿉니다.

using System;

namespace ContosoUniversity.BLL
{
    public class DuplicateAdministratorException : Exception
    {
        public DuplicateAdministratorException(string message)
            : base(message)
        {
        }
    }
}

이제 컴파일할 수 있도록 이전에 테스트 프로젝트에서 만든 임시 DuplicateAdministratorException.cs 파일을 삭제합니다.

기본 프로젝트에서 SchoolBL.cs 파일을 열고 유효성 검사 논리를 포함하는 다음 메서드를 추가합니다. (코드는 나중에 만들 메서드를 참조합니다.)

private void ValidateOneAdministratorAssignmentPerInstructor(Department department)
{
    if (department.Administrator != null)
    {
        var duplicateDepartment = schoolRepository.GetDepartmentsByAdministrator(department.Administrator.GetValueOrDefault()).FirstOrDefault();
        if (duplicateDepartment != null && duplicateDepartment.DepartmentID != department.DepartmentID)
        {
            throw new DuplicateAdministratorException(String.Format(
                "Instructor {0} {1} is already administrator of the {2} department.", 
                duplicateDepartment.Person.FirstMidName, 
                duplicateDepartment.Person.LastName, 
                duplicateDepartment.Name));
        }
    }
}

다른 부서에 동일한 관리자가 있는지 여부를 검사 위해 엔터티를 삽입하거나 업데이트 Department 할 때 이 메서드를 호출합니다.

이 코드는 메서드를 호출하여 데이터베이스에서 Department 삽입 또는 업데이트되는 엔터티와 동일한 Administrator 속성 값을 갖는 엔터티를 검색합니다. 이 코드가 발견되면 코드에서 예외를 throw합니다. 삽입되거나 업데이트되는 엔터티에 값이 없는 경우 유효성 검사 검사 필요하지 않으며Administrator, 업데이트 중에 메서드가 호출되고 발견된 엔터티가 업데이트되는 엔터티와 Department 일치하는 Department 경우 예외가 throw되지 않습니다.

Update 메서드에서 새 메서드를 Insert 호출합니다.

public void InsertDepartment(Department department)
{
    ValidateOneAdministratorAssignmentPerInstructor(department);
    try
    ...

public void UpdateDepartment(Department department, Department origDepartment)
{
    ValidateOneAdministratorAssignmentPerInstructor(department);
    try
    ...

ISchoolRepository.cs에서 새 데이터 액세스 메서드에 대해 다음 선언을 추가합니다.

IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator);

SchoolRepository.cs에서 다음 using 문을 추가합니다.

using System.Data.Objects;

SchoolRepository.cs에서 다음과 같은 새 데이터 액세스 메서드를 추가합니다.

public IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator)
{
    return new ObjectQuery<Department>("SELECT VALUE d FROM Departments as d", context, MergeOption.NoTracking).Include("Person").Where(d => d.Administrator == administrator).ToList();
}

이 코드는 Department 지정된 관리자가 있는 엔터티를 검색합니다. 하나의 부서만 찾아야 합니다(있는 경우). 그러나 데이터베이스에 제약 조건이 기본 제공되지 않으므로 반환 형식은 여러 부서가 있는 경우 컬렉션입니다.

기본적으로 개체 컨텍스트는 데이터베이스에서 엔터티를 검색할 때 개체 상태 관리자에서 해당 엔터티를 추적합니다. 매개 변수는 MergeOption.NoTracking 이 쿼리에 대해 이 추적이 수행되지 않도록 지정합니다. 쿼리가 업데이트하려는 정확한 엔터티를 반환한 다음 해당 엔터티를 연결할 수 없으므로 이 작업이 필요합니다. 예를 들어 Departments.aspx 페이지에서 기록 부서를 편집하고 관리자를 변경하지 않은 상태로 두면 이 쿼리는 기록 부서를 반환합니다. 가 설정되지 않은 경우 NoTracking 개체 컨텍스트의 개체 상태 관리자에 기록 부서 엔터티가 이미 있습니다. 그런 다음 뷰 상태에서 다시 만든 기록 부서 엔터티를 연결하면 개체 컨텍스트에서 라는 예외가 "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key"throw됩니다.

(를 지정하는 MergeOption.NoTracking대신 이 쿼리에 대한 새 개체 컨텍스트를 만들 수 있습니다. 새 개체 컨텍스트에는 자체 개체 상태 관리자가 있으므로 메서드를 호출 Attach 할 때 충돌이 발생하지 않습니다. 새 개체 컨텍스트는 메타데이터 및 데이터베이스 연결을 원래 개체 컨텍스트와 공유하므로 이 대체 방법의 성능 저하는 최소화됩니다. 그러나 여기에 표시된 접근 방식에는 다른 컨텍스트에서 유용하게 사용할 수 있는 옵션이 도입 NoTracking 되었습니다. 이 옵션은 이 NoTracking 시리즈의 이후 자습서에서 자세히 설명합니다.)

테스트 프로젝트에서 MockSchoolRepository.cs에 새 데이터 액세스 메서드를 추가합니다.

public IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator)
{
    return (from d in departments
            where d.Administrator == administrator
            select d);
}

이 코드는 LINQ를 사용하여 프로젝트 리포지토리에서 ContosoUniversity LINQ to Entities 사용하는 것과 동일한 데이터 선택을 수행합니다.

테스트 프로젝트를 다시 실행합니다. 이번에는 테스트가 성공합니다.

Image04

ObjectDataSource 예외 처리

ContosoUniversity 프로젝트에서 Departments.aspx 페이지를 실행하고 부서의 관리자를 다른 부서의 관리자로 변경하려고 합니다. (데이터베이스에 잘못된 데이터가 미리 로드되어 있으므로 이 자습서에서 추가한 부서만 편집할 수 있습니다.) 다음 서버 오류 페이지가 표시됩니다.

Image05

사용자가 이러한 종류의 오류 페이지를 볼 수 없도록 하려면 오류 처리 코드를 추가해야 합니다. Departments.aspx를 열고 의 이벤트에 대한 OnUpdated 처리기를 지정합니다DepartmentsObjectDataSource. 이제 여는 태그는 ObjectDataSource 다음 예제와 유사합니다.

<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.BLL.SchoolBL"
        DataObjectTypeName="ContosoUniversity.DAL.Department" 
        SelectMethod="GetDepartments" 
        DeleteMethod="DeleteDepartment" 
        UpdateMethod="UpdateDepartment"
        ConflictDetection="CompareAllValues"
        OldValuesParameterFormatString="orig{0}" 
        OnUpdated="DepartmentsObjectDataSource_Updated" >

Departments.aspx.cs에서 다음 using 문을 추가합니다.

using ContosoUniversity.BLL;

이벤트에 대해 다음 처리기를 추가합니다 Updated .

protected void DepartmentsObjectDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        if (e.Exception.InnerException is DuplicateAdministratorException)
        {
            var duplicateAdministratorValidator = new CustomValidator();
            duplicateAdministratorValidator.IsValid = false;
            duplicateAdministratorValidator.ErrorMessage = "Update failed: " + e.Exception.InnerException.Message;
            Page.Validators.Add(duplicateAdministratorValidator);
            e.ExceptionHandled = true;
        }
    }
}

컨트롤이 ObjectDataSource 업데이트를 수행하려고 할 때 예외를 catch하는 경우 이벤트 인수(e)의 예외를 이 처리기에 전달합니다. 처리기의 코드는 예외가 중복된 관리자 예외인지 확인합니다. 이 경우 코드는 표시할 컨트롤에 대한 오류 메시지가 포함된 유효성 검사기 컨트롤을 ValidationSummary 만듭니다.

페이지를 실행하고 다른 사람을 두 부서의 관리자로 다시 만들려고 시도합니다. 이번에는 컨트롤에 ValidationSummary 오류 메시지가 표시됩니다.

Image06

DepartmentsAdd.aspx 페이지를 비슷하게 변경합니다. DepartmentsAdd.aspx에서 의 이벤트에 대한 OnInserted 처리기를 지정합니다DepartmentsObjectDataSource. 결과 태그는 다음 예제와 유사합니다.

<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.BLL.SchoolBL" DataObjectTypeName="ContosoUniversity.DAL.Department" 
        InsertMethod="InsertDepartment"  
        OnInserted="DepartmentsObjectDataSource_Inserted">

DepartmentsAdd.aspx.cs에서 동일한 using 문을 추가합니다.

using ContosoUniversity.BLL;

다음 이벤트 처리기를 추가합니다.

protected void DepartmentsObjectDataSource_Inserted(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        if (e.Exception.InnerException is DuplicateAdministratorException)
        {
            var duplicateAdministratorValidator = new CustomValidator();
            duplicateAdministratorValidator.IsValid = false;
            duplicateAdministratorValidator.ErrorMessage = "Insert failed: " + e.Exception.InnerException.Message;
            Page.Validators.Add(duplicateAdministratorValidator);
            e.ExceptionHandled = true;
        }
    }
}

이제 DepartmentsAdd.aspx.cs 페이지를 테스트하여 한 사람을 둘 이상의 부서의 관리자로 만드는 시도도 올바르게 처리하는지 확인할 수 있습니다.

이렇게 하면 Entity Framework에서 컨트롤을 사용하기 ObjectDataSource 위한 리포지토리 패턴 구현에 대한 소개가 완료됩니다. 리포지토리 패턴 및 테스트 가능성에 대한 자세한 내용은 MSDN 백서 테스트 가능성 및 Entity Framework 4.0을 참조하세요.

다음 자습서에서는 애플리케이션에 정렬 및 필터링 기능을 추가하는 방법을 알아봅니다.