이 항목에서는 ADO.NET Entity Framework의 성능 특성에 대해 설명하고 Entity Framework 애플리케이션의 성능을 향상시키는 데 도움이 되는 몇 가지 고려 사항을 제공합니다.
쿼리 실행 단계
Entity Framework에서 쿼리의 성능을 더 잘 이해하기 위해 쿼리가 개념적 모델에 대해 실행되고 데이터를 개체로 반환할 때 발생하는 작업을 이해하는 것이 좋습니다. 다음 표에서는 이 일련의 작업에 대해 설명합니다.
| 수술 | 상대 비용 | 빈도 | 코멘트 |
|---|---|---|---|
| 메타데이터 로드 | 보통 | 각 애플리케이션 도메인에 한 번. | Entity Framework에서 사용하는 모델 및 매핑 메타데이터가 MetadataWorkspace에 로드됩니다. 이 메타데이터는 전역적으로 캐시되며 동일한 애플리케이션 도메인의 ObjectContext 다른 인스턴스에서 사용할 수 있습니다. |
| 데이터베이스 연결 열기 | 보통1 | 필요에 따라. | 데이터베이스에 대한 열린 연결은 중요한 리소스를 사용하므로 Entity Framework는 필요에 따라 데이터베이스 연결을 열고 닫습니다. 연결을 명시적으로 열 수도 있습니다. 자세한 내용은 연결 및 트랜잭션 관리를 참조하세요. |
| 뷰 생성 | 높음 | 각 애플리케이션 도메인에 한 번. (미리 생성할 수 있습니다.) | Entity Framework가 개념적 모델에 대해 쿼리를 실행하거나 데이터 원본에 대한 변경 내용을 저장하려면 먼저 데이터베이스에 액세스하기 위해 로컬 쿼리 뷰 집합을 생성해야 합니다. 이러한 뷰를 생성하는 데 드는 비용이 높기 때문에 뷰를 미리 생성하고 디자인 타임에 프로젝트에 추가할 수 있습니다. 자세한 내용은 방법: 쿼리 성능 향상을 위해 뷰 미리 생성을 참조하세요. |
| 쿼리 준비 | 보통2 | 각 고유 쿼리에 대해 한 번. | 쿼리 명령을 작성하고, 모델 및 매핑 메타데이터를 기반으로 명령 트리를 생성하고, 반환된 데이터의 모양을 정의하는 데 드는 비용을 포함합니다. 이제 Entity SQL 쿼리 명령과 LINQ 쿼리가 모두 캐시되므로 나중에 동일한 쿼리를 실행하는 데 시간이 더 적게 소요됩니다. 컴파일된 LINQ 쿼리를 사용하여 이후 실행에서 이 비용을 줄일 수 있으며 컴파일된 쿼리는 자동으로 캐시되는 LINQ 쿼리보다 더 효율적일 수 있습니다. 자세한 내용은 컴파일된 쿼리(LINQ to Entities)를 참조하세요. LINQ 쿼리 실행에 대한 일반적인 내용은 LINQ to Entities를 참조하세요.
메모: 메모리 내 컬렉션에 연산자를 적용 Enumerable.Contains 하는 LINQ to Entities 쿼리는 자동으로 캐시되지 않습니다. 또한 컴파일된 LINQ 쿼리에서 메모리 내 컬렉션을 매개 변수화할 수 없습니다. |
| 쿼리 실행 | 낮은 수준2 | 각 쿼리에 대해 한 번. | ADO.NET 데이터 공급자를 사용하여 데이터 원본에 대한 명령을 실행하는 비용입니다. 대부분의 데이터 원본은 쿼리 계획을 캐시하므로 나중에 동일한 쿼리를 실행하는 데 더 적은 시간이 걸릴 수 있습니다. |
| 형식 로드 및 유효성 검사 | 낮은3 | 각 ObjectContext 인스턴스에 대해 한 번. | 형식은 개념적 모델이 정의하는 형식에 대해 로드되고 유효성이 검사됩니다. |
| 추적 | 낮은3 | 쿼리가 반환하는 각 개체에 대해 한 번입니다. 4 | 쿼리에서 병합 옵션을 사용하는 NoTracking 경우 이 단계는 성능에 영향을 주지 않습니다. 쿼리에서 AppendOnly, PreserveChanges, 또는 OverwriteChanges 병합 옵션을 사용하는 경우, 쿼리 결과는 ObjectStateManager에서 추적됩니다. 쿼리가 반환하는 추적된 각 개체에 대해 EntityKey가 생성되며, 이는 ObjectStateEntry에서 ObjectStateManager을 생성하는 데 사용됩니다. 기존 ObjectStateEntry 개체를 EntityKey찾을 수 있으면 기존 개체가 반환됩니다. PreserveChanges또는 OverwriteChanges 옵션을 사용하는 경우 개체가 반환되기 전에 업데이트됩니다. 자세한 내용은 ID 확인, 상태 관리 및 변경 내용 추적을 참조하세요. |
| 개체를 구현하기 | 보통3 | 쿼리가 반환하는 각 개체에 대해 한 번입니다. 4 | 반환된 DbDataReader 개체를 읽고 개체를 만들고 클래스의 DbDataRecord 각 인스턴스에 있는 값을 기반으로 하는 속성 값을 설정하는 프로세스입니다. ObjectContext에 개체가 이미 있고 쿼리에서 AppendOnly 또는 PreserveChanges 병합 옵션을 사용하는 경우 이 단계는 성능에 영향을 주지 않습니다. 자세한 내용은 ID 확인, 상태 관리 및 변경 내용 추적을 참조하세요. |
1 데이터 원본 공급자가 연결 풀링을 구현하면 연결을 여는 비용이 풀 전체에 분산됩니다. .NET Provider for SQL Server는 연결 풀링을 지원합니다.
2 쿼리 복잡성이 증가함에 따라 비용이 증가합니다.
3 총 비용은 쿼리에서 반환되는 개체 수에 비례하여 증가합니다.
4 EntityClient 쿼리가 개체 대신 반환되므로 EntityClient 쿼리에는 이 오버헤드가 EntityDataReader 필요하지 않습니다. 자세한 내용은 Entity Framework용 EntityClient 공급자를 참조하세요.
추가 고려 사항
다음은 Entity Framework 애플리케이션의 성능에 영향을 줄 수 있는 다른 고려 사항입니다.
쿼리 실행
쿼리는 리소스를 많이 사용할 수 있으므로 코드의 어느 지점과 쿼리가 실행되는 컴퓨터를 고려합니다.
지연 및 즉시 실행
ObjectQuery<T> 또는 LINQ 쿼리를 생성할 때 쿼리가 즉시 실행되지 않을 수 있습니다. 쿼리 실행은 필요한 결과가 나올 때까지 지연됩니다. 예를 들어, foreach C# 또는 For Each Visual Basic 열거 시나 List<T> 컬렉션을 채우기 위해 할당된 경우에 수행됩니다. 쿼리 실행은 Execute 에 대한 ObjectQuery<T> 메서드를 호출하거나 First 또는 Any 같은 단일 쿼리를 반환하는 LINQ 메서드를 호출하는 즉시 시작됩니다. 자세한 내용은 개체 쿼리 및 쿼리 실행(LINQ to Entities)을 참조하세요.
LINQ 쿼리의 클라이언트 쪽 실행
LINQ 쿼리의 실행은 데이터 원본을 호스트하는 컴퓨터에서 발생하지만 LINQ 쿼리의 일부 부분은 클라이언트 컴퓨터에서 평가될 수 있습니다. 자세한 내용은 쿼리 실행의 스토어 실행 섹션 (LINQ to Entities)을 참조하세요.
쿼리 및 매핑 복잡성
개별 쿼리의 복잡성과 엔터티 모델의 매핑은 쿼리 성능에 큰 영향을 줍니다.
매핑 구조의 복잡성
개념적 모델의 엔터티와 스토리지 모델의 테이블 간의 간단한 일대일 매핑보다 더 복잡한 모델은 일대일 매핑이 있는 모델보다 더 복잡한 명령을 생성합니다.
쿼리 복잡성
데이터 원본에 대해 실행되거나 많은 양의 데이터를 반환하는 명령에서 많은 수의 조인이 필요한 쿼리는 다음과 같은 방법으로 성능에 영향을 줄 수 있습니다.
단순해 보이는 개념적 모델에 대한 쿼리는 데이터 원본에 대해 더 복잡한 쿼리를 실행할 수 있습니다. Entity Framework는 개념적 모델에 대한 쿼리를 데이터 원본에 대한 동등한 쿼리로 변환하기 때문에 발생할 수 있습니다. 개념적 모델의 단일 엔터티 집합이 데이터 원본의 둘 이상의 테이블에 매핑되거나 엔터티 간의 관계가 조인 테이블에 매핑되는 경우 데이터 원본 쿼리에 대해 실행되는 쿼리 명령에 하나 이상의 조인이 필요할 수 있습니다.
비고
주어진 쿼리에 대해 데이터 원본에서 실행되는 명령을 보려면 ToTraceString 클래스의 ObjectQuery<T> 또는 EntityCommand 메서드를 사용하십시오. 자세한 내용은 방법: 스토어 명령 보기를 참조하세요.
중첩된 Entity SQL 쿼리는 서버에 조인을 만들 수 있으며 많은 수의 행을 반환할 수 있습니다.
다음은 프로젝션 절에 중첩된 쿼리의 예입니다.
SELECT c, (SELECT c, (SELECT c FROM AdventureWorksModel.Vendor AS c ) As Inner2 FROM AdventureWorksModel.JobCandidate AS c ) As Inner1 FROM AdventureWorksModel.EmployeeDepartmentHistory AS c또한 이러한 쿼리로 인해 쿼리 파이프라인은 중첩된 쿼리에서 개체가 중복된 단일 쿼리를 생성합니다. 이 때문에 단일 열이 여러 번 복제될 수 있습니다. SQL Server를 비롯한 일부 데이터베이스에서 TempDB 테이블이 매우 커지면 서버 성능이 저하될 수 있습니다. 중첩된 쿼리를 실행할 때는 주의해야 합니다.
많은 양의 데이터를 반환하는 쿼리는 클라이언트가 결과 집합의 크기에 비례하는 방식으로 리소스를 사용하는 작업을 수행하는 경우 성능이 저하될 수 있습니다. 이러한 경우 쿼리에서 반환되는 데이터의 양을 제한하는 것이 좋습니다. 자세한 내용은 방법: 쿼리 결과 페이지를 참조하세요.
Entity Framework에서 자동으로 생성되는 명령은 데이터베이스 개발자가 명시적으로 작성한 유사한 명령보다 더 복잡할 수 있습니다. 데이터 원본에 대해 실행되는 명령을 명시적으로 제어해야 하는 경우 테이블 반환 함수 또는 저장 프로시저에 대한 매핑을 정의하는 것이 좋습니다.
관계
최적의 쿼리 성능을 위해 엔터티 간의 관계를 엔터티 모델의 연결과 데이터 원본의 논리적 관계로 정의해야 합니다.
쿼리 경로
기본적으로 ObjectQuery<T>을 실행할 때 관계를 나타내는 개체는 반환되지만, 관련된 개체는 반환되지 않습니다. 다음 세 가지 방법 중 하나로 관련 개체를 로드할 수 있습니다.
실행되기 전에 ObjectQuery<T> 쿼리 경로를 설정합니다.
개체가 노출하는 탐색 속성의 메서드
Load를 호출합니다.LazyLoadingEnabled에서 ObjectContext 옵션을
true로 설정합니다. 이 작업은 엔터티 데이터 모델 디자이너를 사용하여 개체 계층 코드를 생성할 때 자동으로 수행됩니다. 자세한 내용은 생성된 코드 개요를 참조하세요.
사용할 옵션을 고려할 때는 데이터베이스에 대한 요청 수와 단일 쿼리에서 반환된 데이터 양 간에 절충이 있다는 점에 유의해야 합니다. 자세한 내용은 관련 개체 로드를 참조하세요.
쿼리 경로 사용
쿼리 경로는 쿼리가 반환하는 개체의 그래프를 정의합니다. 쿼리 경로를 정의할 때 경로가 정의하는 모든 개체를 반환하려면 데이터베이스에 대한 단일 요청만 필요합니다. 쿼리 경로를 사용하면 겉보기에 간단한 개체 쿼리에서 데이터 원본에 대해 복잡한 명령이 실행될 수 있습니다. 단일 쿼리에서 관련 개체를 반환하려면 하나 이상의 조인이 필요하기 때문에 이 문제가 발생합니다. 이러한 복잡성은 상속이 있는 엔터티 또는 다 대 다 관계를 포함하는 경로와 같은 복잡한 엔터티 모델에 대한 쿼리에서 더 큽니다.
비고
ToTraceString 메서드를 사용하여 ObjectQuery<T>에 의해 생성될 명령을 확인하십시오. 자세한 내용은 방법: 스토어 명령 보기를 참조하세요.
쿼리 경로에 관련 개체가 너무 많거나 개체에 행 데이터가 너무 많은 경우 데이터 원본이 쿼리를 완료하지 못할 수 있습니다. 이 문제는 쿼리에 데이터 원본의 기능을 초과하는 중간 임시 스토리지가 필요한 경우에 발생합니다. 이 경우 관련 개체를 명시적으로 로드하여 데이터 원본 쿼리의 복잡성을 줄일 수 있습니다.
관련 개체를 명시적으로 로드
탐색 속성에서 Load 또는 EntityCollection<TEntity>를 반환하는 EntityReference<TEntity> 메서드를 호출하여 관련 개체를 명시적으로 로드할 수 있습니다. 개체를 명시적으로 로드하려면 호출할 때마다 Load 데이터베이스에 대한 왕복이 필요합니다.
비고
반환된 개체의 컬렉션을 반복하는 동안 Load을 호출하는 경우, 예를 들어 Visual Basic에서 foreach 문을 사용하는 For Each의 경우, 데이터 원본별 공급자는 단일 연결에서 여러 활성 결과 집합을 지원해야 합니다. SQL Server 데이터베이스의 경우 공급자 연결 문자열의 MultipleActiveResultSets = true 값을 지정해야 합니다.
엔터티에 LoadProperty 속성이 없거나 EntityCollection<TEntity> 속성이 없을 때도 EntityReference<TEntity> 메서드를 사용할 수 있습니다. 이는 POCO 엔터티를 사용할 때 유용합니다.
관련 개체를 명시적으로 로드하면 조인 수가 줄어들고 중복 데이터의 Load 양이 줄어들지만 데이터베이스에 반복적으로 연결해야 하므로 많은 수의 개체를 명시적으로 로드할 때 비용이 많이 들 수 있습니다.
변경 내용 저장
메서드를 SaveChanges 호출할 때, ObjectContext 컨텍스트에서 추가, 수정, 또는 삭제된 모든 개체에 대해 별도의 생성, 수정, 또는 삭제 명령문이 생성됩니다. 이러한 명령은 단일 트랜잭션의 데이터 원본에서 실행됩니다. 쿼리와 마찬가지로 만들기, 업데이트 및 삭제 작업의 성능은 개념적 모델에서 매핑의 복잡성에 따라 달라집니다.
분산 트랜잭션
DTC(분산 트랜잭션 코디네이터)에서 관리하는 리소스가 필요한 명시적 트랜잭션의 작업은 DTC가 필요하지 않은 유사한 작업보다 훨씬 더 비쌉니다. DTC로의 승격은 다음과 같은 경우에 발생합니다.
항상 명시적 트랜잭션을 DTC로 승격시키는 SQL Server 2000 데이터베이스 또는 기타 데이터 원본에 대한 작업을 수행하는 명시적 트랜잭션입니다.
Entity Framework에서 연결을 관리하는 경우 SQL Server 2005에 대한 작업이 있는 명시적 트랜잭션입니다. 이는 단일 트랜잭션 내에서 연결이 닫히고 다시 열릴 때마다 SQL Server 2005가 DTC로 승격되기 때문에 발생합니다. 이는 Entity Framework의 기본 동작입니다. 이 DTC 승격은 SQL Server 2008을 사용할 때 발생하지 않습니다. SQL Server 2005를 사용할 때 이 승격을 방지하려면 트랜잭션 내에서 연결을 명시적으로 열고 닫아야 합니다. 자세한 내용은 연결 및 트랜잭션 관리를 참조하세요.
명시적 트랜잭션은 트랜잭션 내에서 System.Transactions 하나 이상의 작업이 실행될 때 사용됩니다. 자세한 내용은 연결 및 트랜잭션 관리를 참조하세요.
성능 향상 전략
다음 전략을 사용하여 Entity Framework에서 쿼리의 전반적인 성능을 향상시킬 수 있습니다.
뷰 사전 생성
엔터티 모델을 기반으로 뷰를 생성하는 것은 애플리케이션이 쿼리를 처음 실행할 때 상당한 비용입니다. EdmGen.exe 유틸리티를 사용하여 디자인 중에 프로젝트에 추가할 수 있는 Visual Basic 또는 C# 코드 파일로 보기를 미리 생성합니다. 텍스트 템플릿 변환 도구 키트를 사용하여 미리 컴파일된 뷰를 생성할 수도 있습니다. 미리 생성된 뷰는 런타임에 유효성을 검사하여 지정된 엔터티 모델의 현재 버전과 일치하는지 확인합니다. 자세한 내용은 방법: 쿼리 성능 향상을 위해 뷰 미리 생성을 참조하세요.
매우 큰 모델을 사용하는 경우 다음 고려 사항이 적용됩니다.
.NET 메타데이터 형식은 지정된 이진 파일의 사용자 문자열 문자 수를 16,777,215(0xFFFFFF)로 제한합니다. 매우 큰 모델에 대한 뷰를 생성하고 보기 파일이 이 크기 제한에 도달하면 "더 많은 사용자 문자열을 만들기 위한 논리적 공간이 남아 있지 않습니다." 컴파일 오류가 발생합니다. 이 크기 제한은 모든 관리되는 이진 파일에 적용됩니다. 자세한 내용은 크고 복잡한 모델을 사용할 때 오류를 방지하는 방법을 보여 주는 블로그 를 참조하세요.
쿼리에 NoTracking 병합 옵션을 사용하는 것이 좋습니다.
개체 컨텍스트에서 반환된 개체를 추적하는 데 필요한 비용이 있습니다. 개체에 대한 변경 내용을 검색하고 동일한 논리 엔터티에 대한 여러 요청이 동일한 개체 인스턴스를 반환하도록 하려면 개체를 인스턴스에 ObjectContext 연결해야 합니다. 개체를 업데이트하거나 삭제할 계획이 없으며 ID 관리가 필요하지 않은 경우 쿼리를 NoTracking 실행할 때 병합 옵션을 사용하는 것이 좋습니다.
올바른 양의 데이터 반환
일부 시나리오에서는 데이터베이스에 대한 왕복이 적기 때문에 메서드를 사용하여 Include 쿼리 경로를 지정하는 것이 훨씬 빠릅니다. 그러나 다른 시나리오에서는 조인 수가 적은 간단한 쿼리로 인해 데이터의 중복성이 줄어들기 때문에 관련 개체를 로드하기 위해 데이터베이스에 대한 추가 왕복이 더 빠를 수 있습니다. 따라서 관련 개체를 검색하는 다양한 방법의 성능을 테스트하는 것이 좋습니다. 자세한 내용은 관련 개체 로드를 참조하세요.
단일 쿼리에서 너무 많은 데이터를 반환하지 않도록 하려면 쿼리 결과를 관리하기 쉬운 그룹으로 페이징하는 것이 좋습니다. 자세한 내용은 방법: 쿼리 결과 페이지를 참조하세요.
ObjectContext의 범위 제한
대부분의 경우 ObjectContext 문 내에서 using 인스턴스를 만들어야 합니다(Using…End Using Visual Basic). 이렇게 하면 코드가 문 블록을 종료할 때 개체 컨텍스트와 연결된 리소스가 자동으로 삭제되도록 하여 성능을 높일 수 있습니다. 그러나 컨트롤이 개체 컨텍스트에서 관리하는 개체에 바인딩될 때, 바인딩이 필요로 하는 동안 인스턴스를 유지하고 수동으로 삭제해야 합니다. 자세한 내용은 연결 및 트랜잭션 관리를 참조하세요.
데이터베이스 연결을 수동으로 여는 것이 좋습니다.
애플리케이션이 일련의 개체 쿼리를 실행하거나 데이터 원본에 대한 만들기, 업데이트 및 삭제 작업을 유지하기 위해 자주 호출 SaveChanges 하는 경우 Entity Framework는 데이터 원본에 대한 연결을 지속적으로 열고 닫아야 합니다. 이러한 상황에서는 이러한 작업을 시작할 때 수동으로 연결을 열고 작업이 완료되면 연결을 닫거나 삭제하는 것이 좋습니다. 자세한 내용은 연결 및 트랜잭션 관리를 참조하세요.
성능 데이터
Entity Framework의 일부 성능 데이터는 ADO.NET 팀 블로그의 다음 게시물에 게시됩니다.