클라이언트 및 서버 평가Client vs. Server Evaluation

일반적으로 Entity Framework Core는 서버에서 쿼리를 가능한 한 많이 평가하려고 시도합니다.As a general rule, Entity Framework Core attempts to evaluate a query on the server as much as possible. EF Core는 쿼리 부분들을 클라이언트 쪽에서 평가할 수 있는 매개 변수로 변환합니다.EF Core converts parts of the query into parameters, which it can evaluate on the client side. 쿼리의 나머지 부분(과 생성된 매개 변수)은 서버에서 평가할 동등한 데이터베이스 쿼리를 확인할 수 있도록 데이터베이스 공급자에게 제공됩니다.The rest of the query (along with the generated parameters) is given to the database provider to determine the equivalent database query to evaluate on the server. EF Core는 최상위 프로젝션(Select()에 대한 마지막 호출)에서 부분적인 클라이언트 평가를 지원합니다.EF Core supports partial client evaluation in the top-level projection (essentially, the last call to Select()). 쿼리의 최상위 프로젝션이 서버로 변환될 수 없는 경우, EF Core는 서버에서 필요한 데이터를 가져와서 쿼리의 나머지 부분을 클라이언트에서 평가합니다.If the top-level projection in the query can't be translated to the server, EF Core will fetch any required data from the server and evaluate remaining parts of the query on the client. EF Core가 최상위 프로젝션이 아닌 다른 모든 곳에서 서버로 변환할 수 없는 식을 발견하면 런타임 예외를 throw합니다.If EF Core detects an expression, in any place other than the top-level projection, which can't be translated to the server, then it throws a runtime exception. EF Core가 서버로 변환될 수 없는 항목을 판단하는 방식은 쿼리가 작동하는 방법을 참조하세요.See How queries work to understand how EF Core determines what can't be translated to server.

참고

버전 3.0 전까지는 Entity Framework Core가 쿼리의 모든 곳에서 클라이언트 평가를 지원했습니다.Prior to version 3.0, Entity Framework Core supported client evaluation anywhere in the query. 자세한 내용은 이전 버전 섹션을 참조하세요.For more information, see the previous versions section.

GitHub에서 이 문서의 샘플을 볼 수 있습니다.You can view this article's sample on GitHub.

최상위 프로젝션에서의 클라이언트 평가Client evaluation in the top-level projection

다음 예제에서는 도우미 메서드가 SQL Server 데이터베이스에 반환되는 블로그의 URL을 표준화하는 데 사용됩니다.In the following example, a helper method is used to standardize URLs for blogs, which are returned from a SQL Server database. SQL Server 공급자는 이 메서드가 구현되는 방법에 대한 인사이트가 없기 때문에 이 메서드를 SQL로 변환할 수 없습니다.Since the SQL Server provider has no insight into how this method is implemented, it isn't possible to translate it into SQL. 쿼리의 다른 모든 측면은 데이터베이스에서 평가되지만 이 메서드를 통해 반환된 URL을 전달하는 작업은 클라이언트에서 수행됩니다.All other aspects of the query are evaluated in the database, but passing the returned URL through this method is done on the client.

var blogs = context.Blogs
    .OrderByDescending(blog => blog.Rating)
    .Select(blog => new
    {
        Id = blog.BlogId,
        Url = StandardizeUrl(blog.Url)
    })
    .ToList();
public static string StandardizeUrl(string url)
{
    url = url.ToLower();

    if (!url.StartsWith("http://"))
    {
        url = string.Concat("http://", url);
    }

    return url;
}

지원되지 않는 클라이언트 평가Unsupported client evaluation

클라이언트 평가는 유용하긴 하나 때로 성능 저하를 유발할 수 있습니다.While client evaluation is useful, it can result in poor performance sometimes. 도우미 메서드가 where 필터에서 사용되는 다음 쿼리를 살펴보겠습니다.Consider the following query, in which the helper method is now used in a where filter. 필터를 데이터베이스에 적용할 수 없으므로 클라이언트에 필터를 적용하려면 모든 데이터를 메모리로 가져와야 합니다.Because the filter can't be applied in the database, all the data needs to be pulled into memory to apply the filter on the client. 필터와 서버에 있는 데이터의 양에 따라 클라이언트 평가는 성능 저하를 유발할 수 있습니다.Based on the filter and the amount of data on the server, client evaluation could result in poor performance. 따라서 Entity Framework Core는 이러한 클라이언트 평가를 차단하고 런타임 예외를 throw합니다.So Entity Framework Core blocks such client evaluation and throws a runtime exception.

var blogs = context.Blogs
    .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
    .ToList();

명시적 클라이언트 평가Explicit client evaluation

다음과 같은 특정 경우에는 클라이언트 평가를 명시적으로 강제해야 할 수 있습니다.You may need to force into client evaluation explicitly in certain cases like following

  • 데이터의 양이 적어서 클라이언트에서 평가하는 것이 대단한 성능 저하를 유발하지 않는 경우.The amount of data is small so that evaluating on the client doesn't incur a huge performance penalty.
  • 사용되는 LINQ 연산자의 서버 쪽 변환이 없는 경우.The LINQ operator being used has no server-side translation.

위와 같은 경우에는 AsEnumerable 또는 ToList(비동기의 경우 AsAsyncEnumerable 또는 ToListAsync)와 같은 메서드를 호출하여 명시적으로 클라이언트 평가에 옵트인할 수 있습니다.In such cases, you can explicitly opt into client evaluation by calling methods like AsEnumerable or ToList (AsAsyncEnumerable or ToListAsync for async). AsEnumerable을 사용하면 결과를 스트리밍하게 되는 반면 ToList를 사용하면 목록을 생성하여 버퍼를 유발하므로 이로 인해 추가 메모리가 사용됩니다.By using AsEnumerable you would be streaming the results, but using ToList would cause buffering by creating a list, which also takes additional memory. 단, 여러 번 열거할 경우에는 결과를 목록에 저장하는 것이 더 낫습니다. 데이터베이스에 대한 쿼리가 하나밖에 없기 때문입니다.Though if you're enumerating multiple times, then storing results in a list helps more since there's only one query to the database. 구체적인 용도에 따라 어느 메서드가 더 유용한지 판단해야 합니다.Depending on the particular usage, you should evaluate which method is more useful for the case.

var blogs = context.Blogs
    .AsEnumerable()
    .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
    .ToList();

클라이언트 평가의 잠재적인 메모리 누수Potential memory leak in client evaluation

쿼리 변환과 컴파일에는 비용이 많이 들기 때문에 EF Core는 컴파일된 쿼리 계획을 캐시에 저장합니다.Since query translation and compilation are expensive, EF Core caches the compiled query plan. 캐시된 대리자는 최상위 프로젝션의 클라이언트 평가를 수행할 때 클라이언트 코드를 사용할 수 있습니다.The cached delegate may use client code while doing client evaluation of top-level projection. EF Core는 트리에서 클라이언트 평가된 부분에 대한 매개 변수를 생성한 다음 매개 변수 값을 대체하여 쿼리 계획을 재사용합니다.EF Core generates parameters for the client-evaluated parts of the tree and reuses the query plan by replacing the parameter values. 그러나 식 트리의 특정 상수는 매개 변수로 변환할 수 없습니다.But certain constants in the expression tree can't be converted into parameters. 캐시된 대리자가 이러한 상수를 포함하는 경우, 해당 개체는 여전히 참조되고 있으므로 가비지 수집될 수 없습니다.If the cached delegate contains such constants, then those objects can't be garbage collected since they're still being referenced. 이러한 개체가 DbContext 또는 기타 서비스를 포함할 경우, 이로 인해 시간이 흐름에 따라 앱의 메모리 사용량이 늘어날 수 있습니다.If such an object contains a DbContext or other services in it, then it could cause the memory usage of the app to grow over time. 이 동작은 일반적으로 메모리 누수의 신호가 됩니다.This behavior is generally a sign of a memory leak. EF Core는 현재 데이터베이스 공급자를 사용하여 매핑할 수 없는 유형의 상수를 발견할 때마다 예외를 throw합니다.EF Core throws an exception whenever it comes across constants of a type that can't be mapped using current database provider. 일반적인 원인과 해결 방법은 다음과 같습니다.Common causes and their solutions are as follows:

  • 인스턴스 메서드 사용: 클라이언트 프로젝션에서 인스턴스 메서드를 사용하는 경우, 식 트리는 인스턴스의 상수를 포함합니다.Using an instance method: When using instance methods in a client projection, the expression tree contains a constant of the instance. 메서드가 인스턴스의 데이터를 사용하지 않는다면 메서드를 정적으로 만드는 방법을 고려하세요.If your method doesn't use any data from the instance, consider making the method static. 메서드 본문에 인스턴스 데이터가 필요하다면 해당 데이터를 메서드에 인수로 전달하세요.If you need instance data in the method body, then pass the specific data as an argument to the method.
  • 메서드에 상수 인수 전달: 이 경우는 일반적으로 클라이언트 메서드에 대한 인수에서 this를 사용할 때 발생합니다.Passing constant arguments to method: This case arises generally by using this in an argument to client method. 인수를 데이터베이스 공급자에 의해 매핑될 수 있는 여러 개의 스칼라 인수로 분할하는 방안을 고려하세요.Consider splitting the argument in to multiple scalar arguments, which can be mapped by the database provider.
  • 다른 상수: 그 밖의 다른 경우에 상수가 발견될 경우, 해당 상수가 처리에 필요한지 여부를 평가할 수 있습니다.Other constants: If a constant is come across in any other case, then you can evaluate whether the constant is needed in processing. 상수를 가지고 있어야 하거나 위 경우의 해결 방법을 사용할 수 없다면 값을 저장할 지역 변수를 만든 다음 쿼리에서 이 지역 변수를 사용하세요.If it's necessary to have the constant, or if you can't use a solution from the above cases, then create a local variable to store the value and use local variable in the query. EF Core가 지역 변수를 매개 변수로 변환합니다.EF Core will convert the local variable into a parameter.

이전 버전Previous versions

다음 섹션은 3.0 이전의 EF Core 버전에 적용됩니다.The following section applies to EF Core versions before 3.0.

이전의 EF Core 버전에서는 최상위 프로젝션만이 아닌 쿼리의 모든 부분에서 클라이언트 평가를 지원합니다.Older EF Core versions supported client evaluation in any part of the query--not just the top-level projection. 따라서 지원되지 않는 클라이언트 평가 섹션에 있는 것과 비슷한 쿼리가 올바르게 작동했던 것입니다.That's why queries similar to one posted under the Unsupported client evaluation section worked correctly. 이 동작은 발견되지 않은 성능 문제를 야기할 수 있기 때문에 EF Core는 클라이언트 평가 경고를 기록했습니다.Since this behavior could cause unnoticed performance issues, EF Core logged a client evaluation warning. 로그 출력을 보는 방법은 로깅을 참조하세요.For more information on viewing logging output, see Logging.

선택적으로, EF Core에서 클라이언트 평가를 수행할 때(프로젝션에서의 수행은 제외) 기본 동작을 예외 throw하거나 아무것도 하지 않음으로 변경할 수 있었습니다.Optionally EF Core allowed you to change the default behavior to either throw an exception or do nothing when doing client evaluation (except for in the projection). 예외 throw 동작을 선택하면 3.0에서의 동작과 비슷해집니다.The exception throwing behavior would make it similar to the behavior in 3.0. 동작을 변경하려면 컨텍스트의 옵션을 설정할 때 DbContext.OnConfiguring에서(ASP.NET Core를 사용하는 경우에는 Startup.cs에서) 경고를 구성해야 합니다.To change the behavior, you need to configure warnings while setting up the options for your context - typically in DbContext.OnConfiguring, or in Startup.cs if you're using ASP.NET Core.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
        .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}