Entity Framework 4.0 및 ObjectDataSource 컨트롤 사용, 1부: 시작

작성자 : Tom Dykstra

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

Contoso University 샘플 웹 애플리케이션은 Entity Framework 4.0 및 Visual Studio 2010을 사용하여 ASP.NET Web Forms 애플리케이션을 만드는 방법을 보여 줍니다. 샘플 애플리케이션은 가상의 Contoso University에 대한 웹 사이트입니다. 학생 입학, 강좌 개설 및 강사 할당과 같은 기능이 있습니다.

이 자습서에서는 C#의 예제를 보여 줍니다. 다운로드 가능한 샘플에는 C# 및 Visual Basic의 코드가 모두 포함되어 있습니다.

데이터베이스 우선

Entity Framework에서 데이터로 작업할 수 있는 세 가지 방법은 Database First, Model FirstCode First입니다. 이 자습서는 Database First용입니다. 이러한 워크플로 간의 차이점과 시나리오에 가장 적합한 워크플로를 선택하는 방법에 대한 지침은 Entity Framework 개발 워크플로를 참조하세요.

Web Forms

시작 시리즈와 마찬가지로 이 자습서 시리즈는 ASP.NET Web Forms 모델을 사용하며 Visual Studio에서 ASP.NET Web Forms 사용하는 방법을 알고 있다고 가정합니다. 그렇지 않은 경우 ASP.NET 4.5 Web Forms 시작 참조하세요. ASP.NET MVC 프레임워크를 사용하려면 ASP.NET MVC를 사용하여 Entity Framework로 시작 참조하세요.

소프트웨어 버전

자습서에 표시 또한 와 함께 작동합니다.
Windows 7 Windows 8
Visual Studio 2010 Visual Studio 2010 Express for Web. 자습서는 이후 버전의 Visual Studio에서 테스트되지 않았습니다. 메뉴 선택, 대화 상자 및 템플릿에는 여러 가지 차이점이 있습니다.
.NET 4 .NET 4.5는 .NET 4와 이전 버전과 호환되지만 자습서는 .NET 4.5로 테스트되지 않았습니다.
Entity Framework 4 이 자습서는 이후 버전의 Entity Framework에서 테스트되지 않았습니다. Entity Framework 5부터 EF는 기본적으로 DbContext API EF 4.1에 도입된 를 사용합니다. EntityDataSource 컨트롤은 API를 ObjectContext 사용하도록 설계되었습니다. API에서 EntityDataSource 컨트롤을 DbContext 사용하는 방법에 대한 자세한 내용은 이 블로그 게시물을 참조하세요.

질문

자습서와 직접 관련이 없는 질문이 있는 경우 ASP.NET Entity Framework 포럼, Entity Framework및 LINQ to Entities 포럼 또는 StackOverflow.com 게시할 수 있습니다.

EntityDataSource 컨트롤을 사용하면 애플리케이션을 매우 빠르게 만들 수 있지만 일반적으로 .aspx 페이지에서 상당한 양의 비즈니스 논리 및 데이터 액세스 논리를 유지해야 합니다. 애플리케이션의 복잡성이 증가하고 지속적인 유지 관리가 필요한 경우 유지 관리가 더 쉬운 n 계층 또는 계층화된 애플리케이션 구조를 만들기 위해 더 많은 개발 시간을 미리 투자할 수 있습니다. 이 아키텍처를 구현하려면 프레젠테이션 계층을 BLL(비즈니스 논리 계층) 및 DAL(데이터 액세스 계층)과 분리합니다. 이 구조를 구현하는 한 가지 방법은 컨트롤 대신 컨트롤을 사용하는 ObjectDataSource 것입니다 EntityDataSource . 컨트롤을 사용하는 경우 사용자 고유의 ObjectDataSource 데이터 액세스 코드를 구현한 다음 다른 데이터 소스 컨트롤과 동일한 기능이 많은 컨트롤을 사용하여 .aspx 페이지에서 호출합니다. 이렇게 하면 n 계층 접근 방식의 장점과 데이터 액세스에 Web Forms 컨트롤을 사용할 경우의 이점을 결합할 수 있습니다.

컨트롤은 ObjectDataSource 다른 방법으로도 더 많은 유연성을 제공합니다. 사용자 고유의 데이터 액세스 코드를 작성하기 때문에 컨트롤이 수행하도록 설계된 작업인 특정 엔터티 형식을 읽기, 삽입, 업데이트 또는 삭제하는 것 이상을 수행하는 것이 EntityDataSource 더 쉽습니다. 예를 들어 엔터티가 업데이트될 때마다 로깅을 수행하거나, 엔터티가 삭제될 때마다 데이터를 보관하거나, 외래 키 값이 있는 행을 삽입할 때 필요에 따라 관련 데이터를 자동으로 검사 업데이트할 수 있습니다.

비즈니스 논리 및 리포지토리 클래스

컨트롤은 ObjectDataSource 만든 클래스를 호출하여 작동합니다. 클래스에는 데이터를 검색하고 업데이트하는 메서드가 포함되며 태그의 컨트롤에 해당 메서드의 ObjectDataSource 이름을 제공합니다. 렌더링 또는 포스트백 처리 중에 는 ObjectDataSource 지정한 메서드를 호출합니다.

기본 CRUD 작업 외에도 컨트롤과 함께 사용하기 위해 만드는 클래스는 ObjectDataSource 데이터를 읽거나 업데이트할 때 비즈니스 논리를 ObjectDataSource 실행해야 할 수 있습니다. 예를 들어 부서를 업데이트할 때 한 사람이 둘 이상의 부서의 관리자가 될 수 없으므로 다른 부서에 동일한 관리자가 없는지 확인해야 할 수 있습니다.

ObjectDataSource 클래스 개요와 같은 일부 ObjectDataSource 설명서에서 컨트롤은 비즈니스 논리와 데이터 액세스 논리를 모두 포함하는 비즈니스 개체라고 하는 클래스를 호출합니다. 이 자습서에서는 비즈니스 논리 및 데이터 액세스 논리에 대한 별도의 클래스를 만듭니다. 데이터 액세스 논리를 캡슐화하는 클래스를 리포지토리라고 합니다. 비즈니스 논리 클래스에는 비즈니스 논리 메서드와 데이터 액세스 메서드가 모두 포함되지만 데이터 액세스 메서드는 리포지토리를 호출하여 데이터 액세스 작업을 수행합니다.

또한 BLL과 DAL 간에 BLL의 자동화된 단위 테스트를 용이하게 하는 추상화 계층을 만듭니다. 이 추상화 계층은 인터페이스를 만들고 비즈니스 논리 클래스에서 리포지토리를 인스턴스화할 때 인터페이스를 사용하여 구현됩니다. 이렇게 하면 리포지토리 인터페이스를 구현하는 모든 개체에 대한 참조를 비즈니스 논리 클래스에 제공할 수 있습니다. 정상적인 작업의 경우 Entity Framework에서 작동하는 리포지토리 개체를 제공합니다. 테스트를 위해 컬렉션으로 정의된 클래스 변수와 같이 쉽게 조작할 수 있는 방식으로 저장된 데이터와 함께 작동하는 리포지토리 개체를 제공합니다.

다음 그림에서는 리포지토리가 없는 데이터 액세스 논리를 포함하는 비즈니스 논리 클래스와 리포지토리를 사용하는 클래스 간의 차이점을 보여 줍니다.

Image05

먼저 기본 데이터 액세스 작업만 수행하므로 컨트롤이 리포지토리에 직접 바인딩되는 웹 페이지를 ObjectDataSource 만듭니다. 다음 자습서에서는 유효성 검사 논리를 사용하여 비즈니스 논리 클래스를 만들고 컨트롤을 ObjectDataSource 리포지토리 클래스 대신 해당 클래스에 바인딩합니다. 또한 유효성 검사 논리에 대한 단위 테스트를 만듭니다. 이 시리즈의 세 번째 자습서에서는 애플리케이션에 정렬 및 필터링 기능을 추가합니다.

이 자습서에서 만든 페이지는 시작 자습서 시리즈에서 만든 데이터 모델의 엔터티 집합에서 작동 Departments 합니다.

부서 페이지의 모양을 보여 주는 스크린샷

Image02

데이터베이스 및 데이터 모델 업데이트

데이터베이스를 두 가지 변경하여 이 자습서를 시작합니다. 이 두 가지 모두 Entity Framework 및 Web Forms 자습서를 사용하여 시작 만든 데이터 모델을 해당 변경해야 합니다. 이러한 자습서 중 하나에서 데이터베이스 변경 후 데이터 모델을 데이터베이스와 동기화하기 위해 디자이너를 수동으로 변경했습니다. 이 자습서에서는 디자이너의 데이터베이스에서 모델 업데이트 도구를 사용하여 데이터 모델을 자동으로 업데이트합니다.

데이터베이스에 관계 추가

Visual Studio에서 Entity Framework 및 Web Forms 자습서 시리즈를 사용하여 시작 만든 Contoso University 웹 애플리케이션을 연 다음 데이터베이스 다이어그램을 SchoolDiagram 엽니다.

데이터베이스 다이어그램에서 Department 테이블을 보면 열이 있는 것을 Administrator 볼 수 있습니다. 이 열은 테이블의 Person 외래 키이지만 데이터베이스에는 외래 키 관계가 정의되어 있지 않습니다. Entity Framework에서 이 관계를 자동으로 처리할 수 있도록 관계를 만들고 데이터 모델을 업데이트해야 합니다.

데이터베이스 다이어그램에서 테이블을 마우스 오른쪽 단추로 Department 클릭하고 관계를 선택합니다.

Image80

외래 키 관계 상자에서 추가를 클릭한 다음 테이블 및 열 사양에 대한 줄임표를 클릭합니다.

Image81

테이블 및 열 대화 상자에서 기본 키 테이블과 필드를 Person 및 로 PersonID설정하고 외래 키 테이블과 필드를 및 AdministratorDepartment 설정합니다. 이렇게 하면 관계 이름이 에서 FK_Department_DepartmentFK_Department_Person로 변경됩니다.

Image82

테이블 및 열 상자에서 확인을 클릭하고 외래 키 관계 상자에서 닫기를 클릭한 다음 변경 내용을 저장합니다. 및 테이블을 저장할 Person 지 묻는 메시지가 표시되면 예를 클릭합니다.Department

참고

열에 이미 Administrator 있는 데이터에 해당하는 행을 삭제 Person 한 경우 이 변경 사항을 저장할 수 없습니다. 이 경우 서버 Explorer 테이블 편집기를 사용하여 모든 Department 행의 Administrator 값에 테이블에 실제로 존재하는 레코드의 ID가 포함되어 있는지 확인합니다Person.

변경 사항을 저장한 후에는 해당 사용자가 부서 관리자인 경우 테이블에서 행 Person 을 삭제할 수 없습니다. 프로덕션 애플리케이션에서는 데이터베이스 제약 조건이 삭제를 방지하거나 연속 삭제를 지정할 때 특정 오류 메시지를 제공합니다. 연속 삭제를 지정하는 방법에 대한 예제는 Entity Framework 및 ASP.NET – 시작 2부를 참조하세요.

데이터베이스에 뷰 추가

만들려는 새 Departments.aspx 페이지에서 사용자가 부서 관리자를 선택할 수 있도록 이름이 "last, first" 형식인 강사 드롭다운 목록을 제공하려고 합니다. 이 작업을 더 쉽게 수행하려면 데이터베이스에 뷰를 만듭니다. 보기는 드롭다운 목록에 필요한 데이터(전체 이름(올바르게 서식 지정됨) 및 레코드 키로 구성됩니다.

서버 ExplorerSchool.mdf를 확장하고 Views 폴더를 마우스 오른쪽 단추로 클릭한 다음 새 보기 추가를 선택합니다.

Image06

테이블 추가 대화 상자가 나타나면 기를 클릭하고 다음 SQL 문을 SQL 창에 붙여넣습니다.

SELECT        LastName + ',' + FirstName AS FullName, PersonID
FROM          dbo.Person
WHERE        (HireDate IS NOT NULL)

보기를 로 vInstructorName저장합니다.

데이터 모델 업데이트

DAL 폴더에서 SchoolModel.edmx 파일을 열고 디자인 화면을 마우스 오른쪽 단추로 클릭한 다음 데이터베이스에서 모델 업데이트를 선택합니다.

Image07

데이터베이스 개체 선택 대화 상자에서 추가 탭을 선택하고 방금 만든 보기를 선택합니다.

Image08

Finish를 클릭합니다.

디자이너에서 도구가 엔터티와 및 Person 엔터티 간의 Department 새 연결을 만든 vInstructorName 것을 볼 수 있습니다.

Image13

참고

출력오류 목록 창에 도구가 새 vInstructorName 보기에 대한 기본 키를 자동으로 만들었다는 경고 메시지가 표시될 수 있습니다. 이는 정상적인 동작입니다.

코드에서 새 vInstructorName 엔터티를 참조하는 경우 소문자 "v"를 접두사로 지정하는 데이터베이스 규칙을 사용하지 않으려는 것입니다. 따라서 모델에서 엔터티 및 엔터티 집합의 이름을 바꿉니다.

모델 브라우저를 엽니다. vInstructorName 엔터티 형식 및 뷰로 나열됩니다.

Image14

SchoolModel(SchoolModel.Store 아님)에서 vInstructorName을 마우스 오른쪽 단추로 클릭하고 속성을 선택합니다. 속성 창에서 Name 속성을 "InstructorName"으로 변경하고 Entity Set Name 속성을 "InstructorNames"로 변경합니다.

Image15

데이터 모델을 저장하고 닫은 다음 프로젝트를 다시 빌드합니다.

리포지토리 클래스 및 ObjectDataSource 컨트롤 사용

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

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

namespace ContosoUniversity.DAL
{
    public class SchoolRepository : IDisposable
    {
        private SchoolEntities context = new SchoolEntities();

        public IEnumerable<Department> GetDepartments()
        {
            return context.Departments.Include("Person").ToList();
        }

        private bool disposedValue = false;

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

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

    }
}

이 코드는 엔터티 집합의 모든 엔터티를 반환하는 단일 GetDepartments 메서드를 Departments 제공합니다. 반환된 모든 행에 Person 대한 탐색 속성에 액세스한다는 것을 알고 있으므로 메서드를 사용하여 해당 속성에 대한 즉시 로드를 Include 지정합니다. 또한 클래스는 개체가 IDisposable 삭제될 때 데이터베이스 연결이 해제되도록 인터페이스를 구현합니다.

참고

일반적인 방법은 각 엔터티 형식에 대한 리포지토리 클래스를 만드는 것입니다. 이 자습서에서는 여러 엔터티 형식에 대한 하나의 리포지토리 클래스가 사용됩니다. 리포지토리 패턴에 대한 자세한 내용은 Entity Framework 팀의 블로그Julie Lerman 블로그의 게시물을 참조하세요.

메서드는 GetDepartments 리포지토리 개체 자체가 삭제된 후에도 반환된 컬렉션을 사용할 수 있도록 개체가 아닌 IQueryable 개체를 반환 IEnumerable 합니다. 개체에 IQueryable 액세스할 때마다 데이터베이스 액세스가 발생할 수 있지만 데이터 바운드 컨트롤이 데이터를 렌더링하려고 할 때 리포지토리 개체가 삭제될 수 있습니다. 개체 대신 개체와 같은 다른 컬렉션 형식을 IList 반환할 수 있습니다 IEnumerable . 그러나 개체를 IEnumerable 반환하면 루프 및 LINQ 쿼리와 같은 foreach 일반적인 읽기 전용 목록 처리 작업을 수행할 수 있지만 컬렉션의 항목을 추가하거나 제거할 수 없으므로 이러한 변경 내용이 데이터베이스에 유지될 수 있습니다.

Site.Master master 페이지를 사용하는 Departments.aspx 페이지를 만들고 라는 Content2컨트롤에 다음 태그를 Content 추가합니다.

<h2>Departments</h2>
    <asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" 
        DataObjectTypeName="ContosoUniversity.DAL.Department" 
        SelectMethod="GetDepartments" >
    </asp:ObjectDataSource>
    <asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource"  >
        <Columns>
            <asp:CommandField ShowEditButton="True" ShowDeleteButton="True"
                ItemStyle-VerticalAlign="Top">
            </asp:CommandField>
            <asp:DynamicField DataField="Name" HeaderText="Name" SortExpression="Name" ItemStyle-VerticalAlign="Top" />
            <asp:DynamicField DataField="Budget" HeaderText="Budget" SortExpression="Budget" ItemStyle-VerticalAlign="Top" />
            <asp:DynamicField DataField="StartDate" HeaderText="Start Date" ItemStyle-VerticalAlign="Top" />
            <asp:TemplateField HeaderText="Administrator" SortExpression="Person.LastName" ItemStyle-VerticalAlign="Top" >
                <ItemTemplate>
                    <asp:Label ID="AdministratorLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>'></asp:Label>,
                    <asp:Label ID="AdministratorFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>

이 태그는 ObjectDataSource 방금 만든 리포지토리 클래스를 사용하는 컨트롤과 GridView 데이터를 표시하는 컨트롤을 만듭니다. 컨트롤은 GridView편집삭제 명령을 지정하지만 아직 지원하는 코드를 추가하지 않았습니다.

여러 열은 자동 데이터 서식 지정 및 유효성 검사 기능을 활용할 수 있도록 컨트롤을 사용합니다 DynamicField . 이러한 작업이 작동하려면 이벤트 처리기에서 메서드를 EnableDynamicDataPage_Init 호출해야 합니다. (DynamicControl 컨트롤은 탐색 속성에서 Administrator 작동하지 않으므로 필드에서 사용되지 않습니다.)

특성은 Vertical-Align="Top" 나중에 그리드에 중첩된 GridView 컨트롤이 있는 열을 추가할 때 중요해집니다.

Departments.aspx.cs 파일을 열고 다음 using 문을 추가합니다.

using ContosoUniversity.DAL;

그런 다음 페이지의 Init 이벤트에 대해 다음 처리기를 추가합니다.

protected void Page_Init(object sender, EventArgs e)
{
    DepartmentsGridView.EnableDynamicData(typeof(Department));
}

DAL 폴더에서 Department.cs라는 새 클래스 파일을 만들고 기존 코드를 다음 코드로 바꿉니다.

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.DAL
{
    [MetadataType(typeof(DepartmentMetaData))]
    public partial class Department
    {
    }

    public class DepartmentMetaData
    {
        [DataType(DataType.Currency)]
        [Range(0, 1000000, ErrorMessage = "Budget must be less than $1,000,000.00")]
        public Decimal Budget { get; set; }

        [DisplayFormat(DataFormatString="{0:d}",ApplyFormatInEditMode=true)]
        public DateTime StartDate { get; set; }

    }
}

이 코드는 데이터 모델에 메타데이터를 추가합니다. 데이터 형식DecimalBudget 이지만 엔터티의 Department 속성이 실제로 통화를 나타내고 값이 0에서 $1,000,000.00 사이여야 하므로 지정합니다. 또한 속성의 형식을 StartDate mm/dd/yyyy 형식으로 날짜로 지정합니다.

Departments.aspx 페이지를 실행합니다.

실행된 부서 페이지를 보여 주는 스크린샷

예산 또는 시작 날짜 열에 대한 Departments.aspx 페이지 태그에 서식 문자열을 지정하지는 않았지만 Department.cs 파일에서 제공한 메타데이터를 사용하여 컨트롤에서 기본 통화 및 날짜 서식을 적용 DynamicField 했습니다.

삽입 및 삭제 기능 추가

SchoolRepository.cs를 열고 메서드와 Delete 메서드를 만들기 Insert 위해 다음 코드를 추가합니다. 코드에는 메서드에서 사용할 Insert 다음 사용 가능한 레코드 키 값을 계산하는 이라는 GenerateDepartmentID 메서드도 포함되어 있습니다. 데이터베이스가 테이블에 대해 Department 자동으로 계산하도록 구성되지 않았기 때문에 이 작업이 필요합니다.

public void InsertDepartment(Department department)
{
    try
    {
        department.DepartmentID = GenerateDepartmentID();
        context.Departments.AddObject(department);
        context.SaveChanges();
    }
    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
    {
        context.Departments.Attach(department);
        context.Departments.DeleteObject(department);
        context.SaveChanges();
    }
    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;
    }
}

private Int32 GenerateDepartmentID()
{
    Int32 maxDepartmentID = 0;
    var department = (from d in GetDepartments()
                      orderby d.DepartmentID descending
                      select d).FirstOrDefault();
    if (department != null)
    {
        maxDepartmentID = department.DepartmentID + 1;
    }
    return maxDepartmentID;
}

Attach 메서드

메서드는 DeleteDepartment 메모리의 Attach 엔터티와 나타내는 데이터베이스 행 간에 개체 컨텍스트의 개체 상태 관리자에서 유지 관리되는 링크를 다시 설정하기 위해 메서드를 호출합니다. 메서드가 메서드를 호출 SaveChanges 하기 전에 이 문제가 발생해야 합니다.

개체 컨텍스트라는 용어는 엔터티 집합 및 엔터티에 ObjectContext 액세스하는 데 사용하는 클래스에서 파생되는 Entity Framework 클래스를 나타냅니다. 이 프로젝트의 코드에서 클래스의 이름은 SchoolEntities이고, 클래스의 instance 항상 라는 이름이 지정context됩니다. 개체 컨텍스트의 개체 상태 관리자는 클래스에서 ObjectStateManager 파생되는 클래스입니다. 개체 연락처는 개체 상태 관리자를 사용하여 엔터티 개체를 저장하고 각 개체가 데이터베이스의 해당 테이블 행 또는 행과 동기화되어 있는지 여부를 추적합니다.

엔터티를 읽을 때 개체 컨텍스트는 개체 상태 관리자에 저장하고 개체의 표현이 데이터베이스와 동기화되어 있는지 여부를 추적합니다. 예를 들어 속성 값을 변경하면 변경한 속성이 더 이상 데이터베이스와 동기화되지 않음을 나타내도록 플래그가 설정됩니다. 그런 다음 메서드를 SaveChanges 호출하면 개체 상태 관리자가 엔터티의 현재 상태와 데이터베이스 상태 간의 차이점을 정확히 알고 있으므로 개체 컨텍스트에서 데이터베이스에서 수행할 작업을 알 수 있습니다.

그러나 개체 상태 관리자의 모든 항목과 함께 엔터티를 읽는 개체 컨텍스트 instance 페이지가 렌더링된 후 삭제되기 때문에 이 프로세스는 일반적으로 웹 애플리케이션에서 작동하지 않습니다. 변경 내용을 적용해야 하는 개체 컨텍스트 instance 포스트백 처리를 위해 인스턴스화된 새 컨텍스트입니다. 메서드 ObjectDataSourceDeleteDepartment 경우 컨트롤은 뷰 상태의 값에서 원래 버전의 엔터티를 다시 만들지만 이 다시 만든 Department 엔터티는 개체 상태 관리자에 없습니다. 이 다시 만든 엔터티에서 메서드를 호출 DeleteObject 한 경우 개체 컨텍스트가 엔터티가 데이터베이스와 동기화되어 있는지 여부를 모르기 때문에 호출이 실패합니다. 그러나 메서드를 Attach 호출하면 개체 컨텍스트의 이전 instance 엔터티를 읽을 때 원래 자동으로 수행된 데이터베이스의 값과 다시 생성된 엔터티 간에 동일한 추적이 다시 설정됩니다.

개체 컨텍스트가 개체 상태 관리자에서 엔터티를 추적하지 않도록 하고 플래그를 설정하여 해당 작업을 수행하지 않도록 할 수 있는 경우가 있습니다. 이 예제는 이 시리즈의 이후 자습서에 나와 있습니다.

SaveChanges 메서드

이 간단한 리포지토리 클래스는 CRUD 작업을 수행하는 방법의 기본 원칙을 보여 줍니다. 이 예제 SaveChanges 에서 메서드는 각 업데이트 직후 호출됩니다. 프로덕션 애플리케이션에서는 별도의 메서드에서 메서드를 호출 SaveChanges 하여 데이터베이스가 업데이트되는 시기를 더 자세히 제어할 수 있습니다. (다음 자습서의 끝에서는 관련 업데이트를 조정하는 한 가지 방법인 작업 패턴 단위를 설명하는 백서에 대한 링크를 찾을 수 있습니다.) 또한 예제 DeleteDepartment 에서 메서드는 동시성 충돌을 처리하기 위한 코드를 포함하지 않습니다. 이 코드는 이 시리즈의 이후 자습서에서 추가됩니다.

삽입할 때 선택할 강사 이름 검색

사용자는 새 부서를 만들 때 드롭다운 목록의 강사 목록에서 관리자를 선택할 수 있어야 합니다. 따라서 SchoolRepository.cs 에 다음 코드를 추가하여 앞에서 만든 보기를 사용하여 강사 목록을 검색하는 메서드를 만듭니다.

public IEnumerable<InstructorName> GetInstructorNames()
{
    return context.InstructorNames.OrderBy("it.FullName").ToList();
}

부서 삽입을 위한 페이지 만들기

Site.Master 페이지를 사용하는 DepartmentsAdd.aspx 페이지를 만들고 라는 Content2컨트롤에 다음 태그를 Content 추가합니다.

<h2>Departments</h2>
    <asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" DataObjectTypeName="ContosoUniversity.DAL.Department"
        InsertMethod="InsertDepartment" >
    </asp:ObjectDataSource>
    <asp:DetailsView ID="DepartmentsDetailsView" runat="server" 
        DataSourceID="DepartmentsObjectDataSource" AutoGenerateRows="False"
        DefaultMode="Insert" OnItemInserting="DepartmentsDetailsView_ItemInserting">
        <Fields>
            <asp:DynamicField DataField="Name" HeaderText="Name" />
            <asp:DynamicField DataField="Budget" HeaderText="Budget" />
            <asp:DynamicField DataField="StartDate" HeaderText="Start Date" />
            <asp:TemplateField HeaderText="Administrator">
                <InsertItemTemplate>
                    <asp:ObjectDataSource ID="InstructorsObjectDataSource" runat="server" 
                        TypeName="ContosoUniversity.DAL.SchoolRepository" 
                        DataObjectTypeName="ContosoUniversity.DAL.InstructorName"
                        SelectMethod="GetInstructorNames" >
                    </asp:ObjectDataSource>
                    <asp:DropDownList ID="InstructorsDropDownList" runat="server" 
                        DataSourceID="InstructorsObjectDataSource"
                        DataTextField="FullName" DataValueField="PersonID" OnInit="DepartmentsDropDownList_Init">
                    </asp:DropDownList>
                </InsertItemTemplate>
            </asp:TemplateField>
            <asp:CommandField ShowInsertButton="True" />
        </Fields>
    </asp:DetailsView>
   <asp:ValidationSummary ID="DepartmentsValidationSummary" runat="server" 
        ShowSummary="true" DisplayMode="BulletList"  />

이 태그는 새 엔터티를 삽입하는 Department 컨트롤과 부서 관리자를 선택하는 데 사용되는 컨트롤에 대한 DropDownList 강사 이름을 검색하는 컨트롤을 만드는 두 개의 ObjectDataSource 컨트롤을 만듭니다. 태그는 새 부서를 DetailsView 입력하기 위한 컨트롤을 만들고 외래 키 값을 설정할 Administrator 수 있도록 컨트롤의 ItemInserting 이벤트에 대한 처리기를 지정합니다. 끝에는 오류 메시지를 표시하는 컨트롤이 ValidationSummary 있습니다.

DepartmentsAdd.aspx.cs를 열고 다음 using 문을 추가합니다.

using ContosoUniversity.DAL;

다음 클래스 변수 및 메서드를 추가합니다.

private DropDownList administratorsDropDownList;

protected void Page_Init(object sender, EventArgs e)
{
    DepartmentsDetailsView.EnableDynamicData(typeof(Department));
}

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

protected void DepartmentsDetailsView_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
    e.Values["Administrator"] = administratorsDropDownList.SelectedValue;
}

메서드를 Page_Init 사용하면 동적 데이터 기능을 사용할 수 있습니다. 컨트롤의 Init 이벤트에 대한 처리기는 컨트롤에 대한 DropDownList 참조를 저장하고 컨트롤의 Inserting 이벤트에 대한 DetailsView 처리기는 해당 참조를 사용하여 선택한 강사의 값을 가져와 PersonID 엔터티의 Department 외래 키 속성을 업데이트 Administrator 합니다.

페이지를 실행하고 새 부서에 대한 정보를 추가한 다음 삽입 링크를 클릭합니다.

Image04

다른 새 부서의 값을 입력합니다. 예산 필드에 1,000,000.00보다 큰 숫자를 입력하고 탭을 다음 필드로 입력합니다. 별표가 필드에 나타나고 마우스 포인터를 누른 경우 해당 필드의 메타데이터에 입력한 오류 메시지를 볼 수 있습니다.

Image03

삽입을 클릭하면 페이지 아래쪽에 컨트롤이 ValidationSummary 표시하는 오류 메시지가 표시됩니다.

Image12

그런 다음 브라우저를 닫고 Departments.aspx 페이지를 엽니다. 컨트롤에 특성을 추가하고 컨트롤 DataKeyNamesGridView 에 특성을 ObjectDataSource 추가하여 Departments.aspx 페이지에 삭제 기능을 추가 DeleteMethod 합니다. 이러한 컨트롤에 대한 여는 태그는 이제 다음 예제와 유사합니다.

<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" 
        DataObjectTypeName="ContosoUniversity.DAL.Department"
        SelectMethod="GetDepartments" 
        DeleteMethod="DeleteDepartment" >

    <asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource" DataKeyNames="DepartmentID" >

페이지를 실행합니다.

부서 페이지가 실행된 후의 스크린샷

DepartmentsAdd.aspx 페이지를 실행할 때 추가한 부서를 삭제합니다.

업데이트 기능 추가

SchoolRepository.cs를 열고 다음 Update 메서드를 추가합니다.

public void UpdateDepartment(Department department, Department origDepartment)
{
    try
    {
        context.Departments.Attach(origDepartment);
        context.ApplyCurrentValues("Departments", department);
        context.SaveChanges();
    }
    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;
    }
}

Departments.aspx 페이지에서 업데이트를 클릭하면 컨트롤이 ObjectDataSource 메서드에 전달할 두 개의 Department 엔터티를 UpdateDepartment 만듭니다. 하나는 뷰 상태에 저장된 원래 값을 포함하고 다른 하나는 컨트롤에 GridView 입력된 새 값을 포함합니다. 메서드의 UpdateDepartment 코드는 엔터티와 데이터베이스에 있는 항목 간에 추적을 설정하기 위해 원래 값 Attach 이 있는 엔터티를 메서드에 전달 Department 합니다. 그런 다음 코드는 새 값이 있는 엔터티를 메서드에 ApplyCurrentValues 전달 Department 합니다. 개체 컨텍스트는 이전 값과 새 값을 비교합니다. 새 값이 이전 값과 다른 경우 개체 컨텍스트는 속성 값을 변경합니다. 그런 다음 메서드는 SaveChanges 데이터베이스에서 변경된 열만 업데이트합니다. 그러나 이 엔터티의 업데이트 함수가 저장 프로시저에 매핑된 경우 변경된 열에 관계없이 전체 행이 업데이트됩니다.

Departments.aspx 파일을 열고 컨트롤에 다음 특성을 DepartmentsObjectDataSource 추가합니다.

  • UpdateMethod="UpdateDepartment"
  • ConflictDetection="CompareAllValues"
    이렇게 하면 이전 값이 뷰 상태에 저장되어 메서드의 새 값 Update 과 비교할 수 있습니다.
  • OldValuesParameterFormatString="orig{0}"
    이렇게 하면 원래 값 매개 변수의 이름이 임을 컨트롤에 알릴 수 있습니다 origDepartment .

이제 컨트롤의 여는 태그에 대한 태그는 ObjectDataSource 다음 예제와 유사합니다.

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

컨트롤에 OnRowUpdating="DepartmentsGridView_RowUpdating" 특성을 추가합니다 GridView . 이를 사용하여 사용자가 드롭다운 목록에서 선택한 행에 따라 속성 값을 설정합니다 Administrator . 이제 여는 태그는 GridView 다음 예제와 유사합니다.

<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource" DataKeyNames="DepartmentID"
        OnRowUpdating="DepartmentsGridView_RowUpdating">

EditItemTemplate 해당 열에 Administrator 대한 컨트롤 바로 뒤의 GridView 컨트롤에 열에 ItemTemplate 대한 컨트롤을 추가합니다.

<EditItemTemplate>
                    <asp:ObjectDataSource ID="InstructorsObjectDataSource" runat="server" DataObjectTypeName="ContosoUniversity.DAL.InstructorName"
                        SelectMethod="GetInstructorNames" TypeName="ContosoUniversity.DAL.SchoolRepository">
                    </asp:ObjectDataSource>
                    <asp:DropDownList ID="InstructorsDropDownList" runat="server" DataSourceID="InstructorsObjectDataSource"
                        SelectedValue='<%# Eval("Administrator")  %>'
                        DataTextField="FullName" DataValueField="PersonID" OnInit="DepartmentsDropDownList_Init" >
                    </asp:DropDownList>
                </EditItemTemplate>

EditItemTemplate 컨트롤은 DepartmentsAdd.aspx 페이지의 컨트롤과 비슷합니다InsertItemTemplate. 차이점은 컨트롤의 초기 값이 특성을 사용하여 SelectedValue 설정된다는 것입니다.

컨트롤 앞에 GridViewDepartmentsAdd.aspx 페이지에서와 같이 컨트롤을 추가 ValidationSummary 합니다.

<asp:ValidationSummary ID="DepartmentsValidationSummary" runat="server" 
        ShowSummary="true" DisplayMode="BulletList"  />

Departments.aspx.cs를 열고 partial 클래스 선언 직후에 다음 코드를 추가하여 컨트롤을 참조 DropDownList 하는 프라이빗 필드를 만듭니다.

private DropDownList administratorsDropDownList;

그런 다음 컨트롤의 Init 이벤트 및 RowUpdatingGridView 컨트롤의 이벤트에 대한 DropDownList 처리기를 추가합니다.

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

protected void DepartmentsGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    e.NewValues["Administrator"] = administratorsDropDownList.SelectedValue;
}

이벤트에 대한 Init 처리기는 클래스 필드의 컨트롤에 DropDownList 대한 참조를 저장합니다. 이벤트에 대한 RowUpdating 처리기는 참조를 사용하여 사용자가 입력한 값을 가져와서 엔터티의 속성에 AdministratorDepartment 적용합니다.

DepartmentsAdd.aspx 페이지를 사용하여 새 부서를 추가한 다음 Departments.aspx 페이지를 실행하고 추가한 행에서 편집을 클릭합니다.

참고

데이터베이스의 잘못된 데이터로 인해 추가하지 않은 행(즉, 데이터베이스에 이미 있음)을 편집할 수 없습니다. 데이터베이스를 사용하여 만든 행의 관리자는 학생입니다. 이 중 하나를 편집하려고 하면 다음과 같은 오류를 보고하는 오류 페이지가 표시됩니다. 'InstructorsDropDownList' has a SelectedValue which is invalid because it does not exist in the list of items.

Image10

잘못된 예산 금액을 입력한 다음 업데이트를 클릭하면 Departments.aspx 페이지에서 본 것과 동일한 별표와 오류 메시지가 표시됩니다.

필드 값을 변경하거나 다른 관리자를 선택하고 업데이트를 클릭합니다. 변경 내용이 표시됩니다.

부서 페이지를 보여 주는 스크린샷

이렇게 하면 Entity Framework를 사용하여 기본 CRUD(만들기, 읽기, 업데이트, 삭제) 작업에 컨트롤을 사용하는 ObjectDataSource 방법을 소개합니다. 간단한 n 계층 애플리케이션을 빌드했지만 비즈니스 논리 계층은 여전히 데이터 액세스 계층과 긴밀하게 결합되어 자동화된 단위 테스트를 복잡하게 만듭니다. 다음 자습서에서는 단위 테스트를 용이하게 하기 위해 리포지토리 패턴을 구현하는 방법을 알아보세요.