ASP.NET 4 웹 애플리케이션에서 Entity Framework 4.0으로 성능 최대화

작성자: Tom Dykstra

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

이전 자습서에서는 동시성 충돌을 처리하는 방법을 알아보았습니다. 이 자습서에서는 Entity Framework를 사용하는 ASP.NET 웹 애플리케이션의 성능을 향상시키기 위한 옵션을 보여 줍니다. 성능을 극대화하거나 성능 문제를 진단하는 몇 가지 방법을 알아봅니다.

다음 섹션에 제시된 정보는 다양한 시나리오에서 유용할 수 있습니다.

  • 관련 데이터를 효율적으로 로드합니다.
  • 보기 상태를 관리합니다.

성능 문제를 나타내는 개별 쿼리가 있는 경우 다음 섹션에 제시된 정보가 유용할 수 있습니다.

  • NoTracking 병합 옵션을 사용합니다.
  • LINQ 쿼리를 미리 컴파일합니다.
  • 데이터베이스로 전송된 쿼리 명령을 검사합니다.

다음 섹션에 제시된 정보는 매우 큰 데이터 모델이 있는 애플리케이션에 잠재적으로 유용합니다.

  • 뷰를 미리 생성합니다.

참고

웹 애플리케이션 성능은 요청 및 응답 데이터의 크기, 데이터베이스 쿼리 속도, 서버가 큐에 대기할 수 있는 요청 수, 서비스를 제공하는 속도, 사용 중인 클라이언트 스크립트 라이브러리의 효율성 등 여러 요인의 영향을 받습니다. 애플리케이션에서 성능이 중요하거나 테스트 또는 환경에 애플리케이션 성능이 만족스럽지 않은 것으로 표시되는 경우 성능 튜닝을 위한 일반 프로토콜을 따라야 합니다. 성능 병목 현상이 발생하는 위치를 확인한 다음 전체 애플리케이션 성능에 가장 큰 영향을 미칠 영역을 해결하도록 측정합니다.

이 항목에서는 특히 ASP.NET Entity Framework의 성능을 개선할 수 있는 방법에 중점을 둡니다. 여기에 대한 제안은 데이터 액세스가 애플리케이션의 성능 병목 상태 중 하나라고 판단하는 경우에 유용합니다. 앞에서 설명한 경우를 제외하고, 여기에 설명된 방법은 일반적으로 "모범 사례"로 간주되어서는 안 됩니다. 이러한 방법 중 상당수는 예외적인 상황에서만 적절하거나 매우 구체적인 종류의 성능 병목 상태를 해결하는 데 적합합니다.

자습서를 시작하려면 Visual Studio를 시작하고 이전 자습서에서 작업하던 Contoso University 웹 애플리케이션을 엽니다.

Entity Framework는 엔터티의 탐색 속성에 관련 데이터를 로드할 수 있는 여러 가지 방법이 있습니다.

  • 지연 로드. 엔터티를 처음 읽을 때 관련된 데이터가 검색되지 않습니다. 그러나 탐색 속성에 처음으로 액세스하려고 할 때 해당 탐색 속성에 필요한 데이터가 자동으로 검색됩니다. 이렇게 하면 엔터티 자체에 대한 쿼리와 엔터티에 대한 관련 데이터를 검색해야 할 때마다 하나씩 여러 쿼리가 데이터베이스로 전송됩니다.

    Image05

즉시 로드. 엔터티를 읽을 때 관련된 데이터가 함께 검색됩니다. 이는 일반적으로 필요한 데이터를 모두 검색하는 단일 조인 쿼리를 발생시킵니다. 이러한 자습서에서 이미 보았듯이 메서드를 Include 사용하여 즉시 로드를 지정합니다.

Image07

  • 명시적 로드. 코드에서 관련 데이터를 명시적으로 검색한다는 점을 제외하고 지연 로드와 비슷합니다. 탐색 속성에 액세스할 때 자동으로 발생하지 않습니다. 컬렉션에 대한 탐색 속성의 메서드를 사용하여 Load 관련 데이터를 수동으로 로드하거나 단일 개체를 포함하는 속성에 대해 참조 속성의 메서드를 사용합니다 Load . 예를 들어 메서드를 PersonReference.Load 호출하여 엔터티의 PersonDepartment 탐색 속성을 로드합니다.

    Image06

속성 값을 즉시 검색하지 않으므로 지연 로드 및 명시적 로드를 모두 지연 로드라고도 합니다.

지연 로드는 디자이너에서 생성한 개체 컨텍스트의 기본 동작입니다. SchoolModel.Designer 엽니다. 개체 컨텍스트 클래스를 정의하는 cs 파일은 세 가지 생성자 메서드를 찾을 수 있으며 각 메서드에는 다음 문이 포함됩니다.

this.ContextOptions.LazyLoadingEnabled = true;

일반적으로 검색된 모든 엔터티에 대한 관련 데이터가 필요하다는 것을 알고 있는 경우 데이터베이스로 전송되는 단일 쿼리가 검색된 각 엔터티에 대한 별도의 쿼리보다 일반적으로 더 효율적이므로 즉시 로드하면 최상의 성능을 제공합니다. 반면 엔터티의 탐색 속성에 자주 액세스하거나 작은 엔터티 집합에 대해서만 액세스해야 하는 경우 지연 로드 또는 명시적 로드가 더 효율적일 수 있습니다. 즉, 즉시 로드하면 필요한 것보다 더 많은 데이터를 검색할 수 있기 때문입니다.

웹 애플리케이션에서는 관련 데이터의 필요성에 영향을 주는 사용자 작업이 페이지를 렌더링한 개체 컨텍스트에 연결되지 않은 브라우저에서 수행되므로 지연 로드는 값이 상대적으로 적을 수 있습니다. 반면에 컨트롤을 데이터 바인딩할 때는 일반적으로 필요한 데이터를 알고 있으므로 일반적으로 각 시나리오에 적합한 항목에 따라 즉시 로드 또는 지연된 로드를 선택하는 것이 가장 좋습니다.

또한 데이터 바운드 컨트롤은 개체 컨텍스트가 삭제된 후 엔터티 개체를 사용할 수 있습니다. 이 경우 탐색 속성을 지연 로드하려고 하면 실패합니다. 수신하는 오류 메시지는 분명합니다. "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection."

컨트롤은 EntityDataSource 기본적으로 지연 로드를 사용하지 않도록 설정합니다. 현재 자습서에 ObjectDataSource 사용 중인 컨트롤(또는 페이지 코드에서 개체 컨텍스트에 액세스하는 경우)의 경우 기본적으로 지연 로드를 사용하지 않도록 설정할 수 있는 여러 가지 방법이 있습니다. 개체 컨텍스트를 인스턴스화할 때 사용하지 않도록 설정할 수 있습니다. 예를 들어 클래스의 생성자 메서드 SchoolRepository 에 다음 줄을 추가할 수 있습니다.

context.ContextOptions.LazyLoadingEnabled = false;

Contoso University 애플리케이션의 경우 개체 컨텍스트가 자동으로 지연 로드를 사용하지 않도록 설정하여 컨텍스트가 인스턴스화될 때마다 이 속성을 설정할 필요가 없도록 합니다.

SchoolModel.edmx 데이터 모델을 열고 디자인 화면을 클릭한 다음 속성 창에서 지연 로드 사용 속성을 로 False설정합니다. 데이터 모델을 저장하고 닫습니다.

Image04

뷰 상태 관리

업데이트 기능을 제공하려면 ASP.NET 웹 페이지에서 페이지가 렌더링될 때 엔터티의 원래 속성 값을 저장해야 합니다. 포스트백을 처리하는 동안 컨트롤은 엔터티의 원래 상태를 다시 만들고 변경 내용을 적용하고 메서드를 호출하기 전에 엔터티의 Attach 메서드를 호출할 SaveChanges 수 있습니다. 기본적으로 ASP.NET Web Forms 데이터 컨트롤은 뷰 상태를 사용하여 원래 값을 저장합니다. 그러나 보기 상태는 브라우저와 주고 받는 페이지의 크기를 크게 늘릴 수 있는 숨겨진 필드에 저장되므로 성능에 영향을 줄 수 있습니다.

보기 상태를 관리하기 위한 기술 또는 세션 상태와 같은 대안은 Entity Framework에 고유하지 않으므로 이 자습서는 이 항목으로 자세히 설명하지 않습니다. 자세한 내용은 자습서의 끝에 있는 링크를 참조하세요.

그러나 ASP.NET 버전 4는 Web Forms 애플리케이션의 모든 ASP.NET 개발자가 알아야 ViewStateMode 하는 뷰 상태를 사용하는 새로운 방법을 제공합니다. 속성입니다. 이 새 속성은 페이지 또는 컨트롤 수준에서 설정할 수 있으며, 기본적으로 페이지에 대해 보기 상태를 사용하지 않도록 설정하고 필요한 컨트롤에 대해서만 사용하도록 설정할 수 있습니다.

성능이 중요한 애플리케이션의 경우 항상 페이지 수준에서 보기 상태를 사용하지 않도록 설정하고 필요한 컨트롤에 대해서만 사용하도록 설정하는 것이 좋습니다. Contoso University 페이지의 보기 상태 크기는 이 방법으로 크게 줄어들지 않지만 작동 방식을 확인하려면 Instructors.aspx 페이지에 대해 수행합니다. 해당 페이지에는 보기 상태가 비활성화된 컨트롤을 Label 포함하여 많은 컨트롤이 포함되어 있습니다. 이 페이지의 컨트롤 중 어느 것도 실제로 보기 상태를 사용하도록 설정할 필요가 없습니다. 컨트롤의 GridView 속성은 DataKeyNames 포스트백 간에 유지 관리해야 하는 상태를 지정하지만 이러한 값은 속성의 영향을 ViewStateMode 받지 않는 제어 상태로 유지됩니다.

지시문 및 Label 컨트롤 태그는 Page 현재 다음 예제와 유사합니다.

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
    CodeBehind="Instructors.aspx.cs" Inherits="ContosoUniversity.Instructors" %>
    ...
    <asp:Label ID="ErrorMessageLabel" runat="server" Text="" Visible="false" ViewStateMode="Disabled"></asp:Label> 
    ...

다음과 같이 변경합니다.

  • 지시문에 를 추가 ViewStateMode="Disabled" 합니다 Page .
  • 컨트롤에서 제거 ViewStateMode="Disabled" 합니다 Label .

이제 태그는 다음 예제와 유사합니다.

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
    CodeBehind="Instructors.aspx.cs" Inherits="ContosoUniversity.Instructors" 
    ViewStateMode="Disabled" %>
    ...
    <asp:Label ID="ErrorMessageLabel" runat="server" Text="" Visible="false"></asp:Label> 
    ...

이제 모든 컨트롤에 대해 보기 상태를 사용할 수 없습니다. 나중에 뷰 상태를 사용해야 하는 컨트롤을 추가하는 경우 해당 컨트롤에 ViewStateMode="Enabled" 대한 특성을 포함하기만 하면 됩니다.

병합 없음 옵션 사용

개체 컨텍스트가 데이터베이스 행을 검색하고 이를 나타내는 엔터티 개체를 만들 때 기본적으로 개체 상태 관리자를 사용하여 해당 엔터티 개체를 추적합니다. 이 추적 데이터는 캐시 역할을 하며 엔터티를 업데이트할 때 사용됩니다. 웹 애플리케이션에는 일반적으로 수명이 짧은 개체 컨텍스트 인스턴스가 있으므로 쿼리는 추적할 필요가 없는 데이터를 반환하는 경우가 많습니다. 읽는 개체 컨텍스트는 읽는 엔터티를 다시 사용하거나 업데이트하기 전에 삭제되기 때문입니다.

Entity Framework에서 개체 컨텍스트가 병합 옵션을 설정하여 엔터티 개체를 추적할지 여부를 지정할 수 있습니다. 개별 쿼리 또는 엔터티 집합에 대한 병합 옵션을 설정할 수 있습니다. 엔터티 집합에 대해 설정하는 경우 해당 엔터티 집합에 대해 만들어진 모든 쿼리에 대한 기본 병합 옵션을 설정하는 것입니다.

Contoso University 애플리케이션의 경우 리포지토리에서 액세스하는 엔터티 집합에는 추적이 필요하지 않으므로 리포지토리 클래스에서 개체 컨텍스트를 인스턴스화할 때 해당 엔터티 집합에 대해 병합 옵션을 NoTracking 로 설정할 수 있습니다. (이 자습서에서는 병합 옵션을 설정해도 애플리케이션의 성능에 큰 영향을 주지 않습니다. 이 NoTracking 옵션은 특정 대용량 데이터 볼륨 시나리오에서만 관찰 가능한 성능을 개선할 수 있습니다.)

DAL 폴더에서 SchoolRepository.cs 파일을 열고 리포지토리가 액세스하는 엔터티 집합에 대한 병합 옵션을 설정하는 생성자 메서드를 추가합니다.

public SchoolRepository()
{
    context.Departments.MergeOption = MergeOption.NoTracking;
    context.InstructorNames.MergeOption = MergeOption.NoTracking;
    context.OfficeAssignments.MergeOption = MergeOption.NoTracking;
}

LINQ 쿼리 사전 컴파일

Entity Framework가 지정된 ObjectContext instance 수명 내에 Entity SQL 쿼리를 처음 실행하는 경우 쿼리를 컴파일하는 데 다소 시간이 걸립니다. 컴파일 결과는 캐시되므로 쿼리의 후속 실행이 훨씬 빠릅니다. LINQ 쿼리는 쿼리를 컴파일하는 데 필요한 일부 작업이 쿼리가 실행될 때마다 수행된다는 점을 제외하고 비슷한 패턴을 따릅니다. 즉, LINQ 쿼리의 경우 기본적으로 컴파일 결과가 모두 캐시되지는 않습니다.

개체 컨텍스트의 수명 동안 반복적으로 실행될 것으로 예상되는 LINQ 쿼리가 있는 경우 LINQ 쿼리를 처음 실행할 때 컴파일의 모든 결과가 캐시되도록 하는 코드를 작성할 수 있습니다.

예를 들어 클래스의 두 Get 메서드에 SchoolRepository 대해 이 작업을 수행합니다. 그 중 하나는 매개 변수( GetInstructorNames 메서드)를 수행하지 않으며 매개 변수( GetDepartmentsByAdministrator 메서드)가 필요한 메서드입니다. 이제 이러한 메서드는 LINQ 쿼리가 아니므로 실제로 컴파일할 필요가 없습니다.

public IEnumerable<InstructorName> GetInstructorNames()
{
    return context.InstructorNames.OrderBy("it.FullName").ToList();
}
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();
}

그러나 컴파일된 쿼리를 사용해 볼 수 있도록 다음 LINQ 쿼리로 작성된 것처럼 진행합니다.

public IEnumerable<InstructorName> GetInstructorNames()
{
    return (from i in context.InstructorNames orderby i.FullName select i).ToList();
}
public IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator)
{
    context.Departments.MergeOption = MergeOption.NoTracking;
    return (from d in context.Departments where d.Administrator == administrator select d).ToList();
}

이러한 메서드의 코드를 위에 표시된 코드로 변경하고 애플리케이션을 실행하여 계속하기 전에 작동하는지 확인할 수 있습니다. 그러나 다음 지침은 미리 컴파일된 버전을 만드는 것으로 바로 이동합니다.

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Objects;

namespace ContosoUniversity.DAL
{
    public partial class SchoolEntities
    {
        private static readonly Func<SchoolEntities, IQueryable<InstructorName>> compiledInstructorNamesQuery =
            CompiledQuery.Compile((SchoolEntities context) => from i in context.InstructorNames orderby i.FullName select i);

        public IEnumerable<InstructorName> CompiledInstructorNamesQuery()
        {
            return compiledInstructorNamesQuery(this).ToList();
        }

        private static readonly Func<SchoolEntities, Int32, IQueryable<Department>> compiledDepartmentsByAdministratorQuery =
            CompiledQuery.Compile((SchoolEntities context, Int32 administrator) => from d in context.Departments.Include("Person") where d.Administrator == administrator select d);

        public IEnumerable<Department> CompiledDepartmentsByAdministratorQuery(Int32 administrator)
        {
            return compiledDepartmentsByAdministratorQuery(this, administrator).ToList();
        }
    }
}

이 코드는 자동으로 생성된 개체 컨텍스트 클래스를 확장하는 partial 클래스를 만듭니다. partial 클래스에는 클래스의 메서드를 사용하여 Compile 컴파일된 두 개의 LINQ 쿼리가 CompiledQuery 포함됩니다. 또한 쿼리를 호출하는 데 사용할 수 있는 메서드를 만듭니다. 이 파일을 저장하고 닫습니다.

그런 다음 SchoolRepository.cs에서 컴파일된 쿼리를 호출하도록 리포지토리 클래스의 기존 GetInstructorNamesGetDepartmentsByAdministrator 메서드를 변경합니다.

public IEnumerable<InstructorName> GetInstructorNames()
{
    return context.CompiledInstructorNamesQuery();
}
public IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator)
{
    return context.CompiledDepartmentsByAdministratorQuery(administrator);
}

Departments.aspx 페이지를 실행하여 이전과 같이 작동하는지 확인합니다. 메서드는 GetInstructorNames 관리자 드롭다운 목록을 채우기 위해 호출되며 GetDepartmentsByAdministrator , 둘 이상의 부서의 관리자가 강사가 없는지 확인하기 위해 업데이트를 클릭하면 메서드가 호출됩니다.

Image03

Contoso University 애플리케이션에서 쿼리를 미리 컴파일하여 성능이 크게 향상되는 것이 아니라 수행하는 방법을 확인했습니다. LINQ 쿼리를 미리 컴파일하면 코드에 복잡성 수준이 추가되므로 애플리케이션의 성능 병목 상태를 실제로 나타내는 쿼리에 대해서만 수행해야 합니다.

데이터베이스로 전송된 쿼리 검사

성능 문제를 조사할 때 Entity Framework가 데이터베이스에 보내는 정확한 SQL 명령을 아는 것이 도움이 되는 경우가 있습니다. 개체로 작업하는 IQueryable 경우 이 작업을 수행하는 한 가지 방법은 메서드를 ToTraceString 사용하는 것입니다.

SchoolRepository.cs에서 다음 예제와 일치하도록 메서드의 GetDepartmentsByName 코드를 변경합니다.

public IEnumerable<Department> GetDepartmentsByName(string sortExpression, string nameSearchString)
{
    ...
    var departments = new ObjectQuery<Department>("SELECT VALUE d FROM Departments AS d", context).OrderBy("it." + sortExpression).Include("Person").Include("Courses").Where(d => d.Name.Contains(nameSearchString));
    string commandText = ((ObjectQuery)departments).ToTraceString();
    return departments.ToList();
}

departments 이전 줄의 끝에 있는 ObjectQuery 메서드가 개체를 만들기 IQueryable 때문에 Where 변수를 형식으로 캐스팅해야 합니다. 메서드가 Where 없으면 캐스트가 필요하지 않습니다.

줄에 return 중단점을 설정한 다음 디버거에서 Departments.aspx 페이지를 실행합니다. 중단점에 도달하면 지역 창에서 변수 검사 commandText 하고 텍스트 시각화 도우미( 열의 돋보기)를 사용하여 텍스트 시각화 도우미 창에 해당 값을 표시합니다. 이 코드에서 발생하는 전체 SQL 명령을 볼 수 있습니다.

Image08

또는 Visual Studio Ultimate IntelliTrace 기능을 사용하면 코드를 변경하거나 중단점을 설정할 필요가 없는 Entity Framework에서 생성된 SQL 명령을 볼 수 있습니다.

참고

Visual Studio Ultimate 경우에만 다음 절차를 수행할 수 있습니다.

메서드에서 GetDepartmentsByName 원래 코드를 복원한 다음 디버거에서 Departments.aspx 페이지를 실행합니다.

Visual Studio에서 디버그 메뉴, IntelliTrace, IntelliTrace 이벤트를 차례로 선택합니다.

Image11

IntelliTrace 창에서 모두 중단을 클릭합니다.

Image12

IntelliTrace 창에는 최근 이벤트 목록이 표시됩니다.

Image09

ADO.NET 줄을 클릭합니다. 명령 텍스트를 표시하도록 확장됩니다.

Image10

로컬 창에서 전체 명령 텍스트 문자열을 클립보드에 복사할 수 있습니다 .

단순 School 데이터베이스보다 테이블, 관계 및 열이 더 많은 데이터베이스를 사용한다고 가정해 보겠습니다. 여러 Join 절을 포함하는 단일 Select 문에 필요한 모든 정보를 수집하는 쿼리가 너무 복잡하여 효율적으로 작동하지 않을 수 있습니다. 이 경우 즉시 로드에서 명시적 로드로 전환하여 쿼리를 간소화할 수 있습니다.

예를 들어 SchoolRepository.csGetDepartmentsByName 메서드에서 코드를 변경해 보세요. 현재 해당 메서드에는 및 Courses 탐색 속성에 대한 메서드가 있는 Include 개체 쿼리가 Person 있습니다. return 다음 예제와 같이 문을 명시적 로드를 수행하는 코드로 바꿉니다.

public IEnumerable<Department> GetDepartmentsByName(string sortExpression, string nameSearchString)
{
    ...
    var departments = new ObjectQuery<Department>("SELECT VALUE d FROM Departments AS d", context).OrderBy("it." + sortExpression).Where(d => d.Name.Contains(nameSearchString)).ToList();
    foreach (Department d in departments)
    {
        d.Courses.Load();
        d.PersonReference.Load();
    }
    return departments;
}

디버거에서 Departments.aspx 페이지를 실행하고 이전과 마찬가지로 IntelliTrace 창을 다시 검사. 이제 이전에 단일 쿼리가 있었던 경우 긴 시퀀스가 표시됩니다.

이미지13

첫 번째 ADO.NET 줄을 클릭하여 이전에 본 복잡한 쿼리의 결과를 확인합니다.

이미지14

부서의 쿼리는 절이 없는 Join 간단한 Select 쿼리가 되었지만 원래 쿼리에서 반환된 각 부서에 대해 두 개의 쿼리 집합을 사용하여 관련 과정과 관리자를 검색하는 별도의 쿼리가 뒤따릅니다.

참고

지연 로드를 사용하도록 설정한 상태로 두면 동일한 쿼리를 여러 번 반복하여 여기에 표시되는 패턴이 지연 로드로 인해 발생할 수 있습니다. 일반적으로 방지하려는 패턴은 기본 테이블의 모든 행에 대한 관련 데이터를 지연 로드하는 것입니다. 단일 조인 쿼리가 너무 복잡하여 효율적이지 않은 경우 일반적으로 기본 쿼리를 즉시 로드를 사용하도록 변경하여 성능을 향상시킬 수 있습니다.

미리 생성된 뷰

개체가 ObjectContext 새 애플리케이션 도메인에서 처음 만들어지면 Entity Framework는 데이터베이스에 액세스하는 데 사용하는 클래스 집합을 생성합니다. 이러한 클래스를 라고 하며, 매우 큰 데이터 모델이 있는 경우 이러한 보기를 생성하면 새 애플리케이션 도메인이 초기화된 후 페이지에 대한 첫 번째 요청에 대한 웹 사이트의 응답이 지연될 수 있습니다. 런타임이 아닌 컴파일 시간에 뷰를 만들어 이 첫 번째 요청 지연을 줄일 수 있습니다.

참고

애플리케이션에 매우 큰 데이터 모델이 없거나 큰 데이터 모델이 있지만 IIS를 재활용한 후 첫 번째 페이지 요청에만 영향을 주는 성능 문제에 대해 걱정하지 않는 경우 이 섹션을 건너뛸 수 있습니다. 뷰는 애플리케이션 도메인에 캐시되므로 개체를 ObjectContext 인스턴스화할 때마다 보기 만들기가 수행되지 않습니다. 따라서 IIS에서 애플리케이션을 자주 재활용하지 않는 한 미리 생성된 보기의 이점을 얻을 수 있는 페이지 요청은 거의 없습니다.

EdmGen.exe 명령줄 도구를 사용하거나 T4(텍스트 템플릿 변환 도구 키트) 템플릿을 사용하여 보기를 미리 생성할 수 있습니다. 이 자습서에서는 T4 템플릿을 사용합니다.

DAL 폴더에서 텍스트 템플릿 템플릿(설치된 템플릿 목록의 일반 노드 아래에 있음)을 사용하여 파일을 추가하고 이름을 SchoolModel.Views.tt. 파일의 기존 코드를 다음 코드로 바꿉다.

<#
/***************************************************************************

Copyright (c) Microsoft Corporation. All rights reserved.

THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

***************************************************************************/
#>

<#
    //
    // TITLE: T4 template to generate views for an EDMX file in a C# project
    //
    // DESCRIPTION:
    // This is a T4 template to generate views in C# for an EDMX file in C# projects.
    // The generated views are automatically compiled into the project's output assembly.
    //
    // This template follows a simple file naming convention to determine the EDMX file to process:
    // - It assumes that [edmx-file-name].Views.tt will process and generate views for [edmx-file-name].EDMX
    // - The views are generated in the code behind file [edmx-file-name].Views.cs
    //
    // USAGE:
    // Do the following to generate views for an EDMX file (e.g. Model1.edmx) in a C# project
    // 1. In Solution Explorer, right-click the project node and choose "Add...Existing...Item" from the context menu
    // 2. Browse to and choose this .tt file to include it in the project 
    // 3. Ensure this .tt file is in the same directory as the EDMX file to process 
    // 4. In Solution Explorer, rename this .tt file to the form [edmx-file-name].Views.tt (e.g. Model1.Views.tt)
    // 5. In Solution Explorer, right-click Model1.Views.tt and choose "Run Custom Tool" to generate the views
    // 6. The views are generated in the code behind file Model1.Views.cs
    //
    // TIPS:
    // If you have multiple EDMX files in your project then make as many copies of this .tt file and rename appropriately
    // to pair each with each EDMX file.
    //
    // To generate views for all EDMX files in the solution, click the "Transform All Templates" button in the Solution Explorer toolbar
    // (its the rightmost button in the toolbar) 
    //
#>
<#
    //
    // T4 template code follows
    //
#>
<#@ template language="C#" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#>
<#@ output extension=".cs" #>
<# 
    // Find EDMX file to process: Model1.Views.tt generates views for Model1.EDMX
    string edmxFileName = Path.GetFileNameWithoutExtension(this.Host.TemplateFile).ToLowerInvariant().Replace(".views", "") + ".edmx";
    string edmxFilePath = Path.Combine(Path.GetDirectoryName(this.Host.TemplateFile), edmxFileName);
    if (File.Exists(edmxFilePath))
    {
        // Call helper class to generate pre-compiled views and write to output
        this.WriteLine(GenerateViews(edmxFilePath));
    }
    else
    {
        this.Error(String.Format("No views were generated. Cannot find file {0}. Ensure the project has an EDMX file and the file name of the .tt file is of the form [edmx-file-name].Views.tt", edmxFilePath));
    }
    
    // All done!
#>

<#+
    private String GenerateViews(string edmxFilePath)
    {
        MetadataLoader loader = new MetadataLoader(this);
        MetadataWorkspace workspace;
        if(!loader.TryLoadAllMetadata(edmxFilePath, out workspace))
        {
            this.Error("Error in the metadata");
            return String.Empty;
        }
            
        String generatedViews = String.Empty;
        try
        {
            using (StreamWriter writer = new StreamWriter(new MemoryStream()))
            {
                StorageMappingItemCollection mappingItems = (StorageMappingItemCollection)workspace.GetItemCollection(DataSpace.CSSpace);

                // Initialize the view generator to generate views in C#
                EntityViewGenerator viewGenerator = new EntityViewGenerator();
                viewGenerator.LanguageOption = LanguageOption.GenerateCSharpCode;
                IList<EdmSchemaError> errors = viewGenerator.GenerateViews(mappingItems, writer);

                foreach (EdmSchemaError e in errors)
                {
                    // log error
                    this.Error(e.Message);
                }

                MemoryStream memStream = writer.BaseStream as MemoryStream;
                generatedViews = Encoding.UTF8.GetString(memStream.ToArray());
            }
        }
        catch (Exception ex)
        {
            // log error
            this.Error(ex.ToString());
        }

        return generatedViews;
    }
#>

이 코드는 템플릿과 동일한 폴더에 있고 템플릿 파일과 이름이 같은 .edmx 파일에 대한 뷰를 생성합니다. 예를 들어 템플릿 파일 이름이 SchoolModel.Views.tt 경우 SchoolModel.edmx라는 데이터 모델 파일을 찾습니다.

파일을 저장한 다음 솔루션 탐색기 파일을 마우스 오른쪽 단추로 클릭하고 사용자 지정 도구 실행을 선택합니다.

Image02

Visual Studio는 템플릿을 기반으로 하는 SchoolModel.Views.cs 라는 뷰를 만드는 코드 파일을 생성합니다. (템플릿 파일을 저장하는 즉시 사용자 지정 도구 실행을 선택하기 전에 코드 파일이 생성되는 것을 알 수 있습니다.)

Image01

이제 애플리케이션을 실행하고 이전과 같이 작동하는지 확인할 수 있습니다.

미리 생성된 뷰에 대한 자세한 내용은 다음 리소스를 참조하세요.

이렇게 하면 Entity Framework를 사용하는 ASP.NET 웹 애플리케이션의 성능 향상에 대한 소개가 완료됩니다. 자세한 내용은 다음 자료를 참조하세요.

다음 자습서에서는 버전 4의 새로운 Entity Framework에 대한 몇 가지 중요한 개선 사항을 검토합니다.