Contains () 연산자가 Entity Framework의 성능을 크게 저하시키는 이유는 무엇입니까?


79

업데이트 3 : 이 발표 에 따르면 EF6 알파 2의 EF 팀이이 문제를 해결했습니다.

업데이트 2 :이 문제를 해결하기위한 제안을 만들었습니다. 투표하려면 여기로 이동하세요 .

하나의 매우 간단한 테이블이있는 SQL 데이터베이스를 고려하십시오.

CREATE TABLE Main (Id INT PRIMARY KEY)

테이블을 10,000 개의 레코드로 채 웁니다.

WITH Numbers AS
(
  SELECT 1 AS Id
  UNION ALL
  SELECT Id + 1 AS Id FROM Numbers WHERE Id <= 10000
)
INSERT Main (Id)
SELECT Id FROM Numbers
OPTION (MAXRECURSION 0)

테이블에 대한 EF 모델을 빌드하고 LINQPad에서 다음 쿼리를 실행합니다 ( "C # Statements"모드를 사용하므로 LINQPad가 자동으로 덤프를 생성하지 않습니다).

var rows = 
  Main
  .ToArray();

실행 시간은 ~ 0.07 초입니다. 이제 Contains 연산자를 추가하고 쿼리를 다시 실행합니다.

var ids = Main.Select(a => a.Id).ToArray();
var rows = 
  Main
  .Where (a => ids.Contains(a.Id))
  .ToArray();

이 경우 실행 시간은 20.14 초 (288 배 느림)입니다!

처음에는 쿼리를 위해 내 보낸 T-SQL이 실행하는 데 시간이 더 오래 걸린다고 생각했기 때문에 LINQPad의 SQL 창에서 잘라내어 SQL Server Management Studio로 붙여 넣었습니다.

SET NOCOUNT ON
SET STATISTICS TIME ON
SELECT 
[Extent1].[Id] AS [Id]
FROM [dbo].[Primary] AS [Extent1]
WHERE [Extent1].[Id] IN (1,2,3,4,5,6,7,8,...

결과는

SQL Server Execution Times:
  CPU time = 0 ms,  elapsed time = 88 ms.

다음으로 LINQPad가 문제의 원인이라고 생각했지만 LINQPad에서 실행하든 콘솔 애플리케이션에서 실행하든 성능은 동일합니다.

따라서 문제는 Entity Framework 내 어딘가에있는 것으로 보입니다.

내가 여기서 뭔가 잘못하고 있니? 이것은 내 코드에서 시간이 중요한 부분이므로 성능을 높이기 위해 할 수있는 일이 있습니까?

Entity Framework 4.1 및 Sql Server 2008 R2를 사용하고 있습니다.

업데이트 1 :

아래 토론에는 EF가 초기 쿼리를 작성하는 동안 지연이 발생했는지 아니면 다시받은 데이터를 구문 분석하는 동안 발생했는지에 대한 몇 가지 질문이있었습니다. 이것을 테스트하기 위해 다음 코드를 실행했습니다.

var ids = Main.Select(a => a.Id).ToArray();
var rows = 
  (ObjectQuery<MainRow>)
  Main
  .Where (a => ids.Contains(a.Id));
var sql = rows.ToTraceString();

이는 EF가 데이터베이스에 대해 실행하지 않고 쿼리를 생성하도록합니다. 그 결과이 코드를 실행하는 데 ~ 20 개의 secord가 필요하므로 초기 쿼리를 작성하는 데 거의 모든 시간이 소요되는 것으로 보입니다.

CompiledQuery를 구출하려면? 그리 빠르지 않습니다. CompiledQuery는 쿼리에 전달 된 매개 변수가 기본 유형 (int, string, float 등)이어야합니다. 배열 또는 IEnumerable을 허용하지 않으므로 ID 목록에 사용할 수 없습니다.


1
var qry = Main.Where (a => ids.Contains(a.Id)); var rows = qry.ToArray();쿼리의 어느 부분이 시간이 걸리는지 확인 하려고 했습니까 ?
Andrew Cooper

쿼리를 저하시키는 것은 EF가 아니라 실행하려는 실제 쿼리입니다. 무엇을 하려는지 설명해 주시겠습니까? 아마 당신의 요구에 더 나은 방법이있다
크리스 이바노프

@AndrewCooper 방금 시도했고 지연된 실행으로 인해 ToArray가없는 첫 번째 명령문이 거의 즉시 실행됩니다. 포함 필터링을 포함한 쿼리는 ToArray ()를 실행할 때까지 실제로 실행되지 않습니다.
Mike

5
이것에 대한 업데이트 : EF6 alpha 2에는 Enumerable.Contains의 번역을 가속화하는 개선 사항이 포함되어 있습니다. 공지 사항은 blogs.msdn.com/b/adonet/archive/2012/12/10/…에서 확인하십시오 . 내 테스트에 따르면 100,000 개의 int 요소가있는 목록에 대해 list.Contains (x)를 번역하는 데 1 초도 걸리지 않으며 시간은 목록의 요소 수에 따라 거의 선형 적으로 증가합니다. 귀하의 피드백과 EF 개선에 도움을 주셔서 감사합니다!
divega

1
이 점에주의하십시오 ... 모든 IEnumerable 매개 변수가있는 쿼리는 캐시 할 수 없으므로 쿼리 계획이 복잡 할 때 심각한 부작용이 발생할 수 있습니다. 작업을 여러 번 실행해야하는 경우 (예 : 데이터 청크를 얻기 위해 포함을 사용하는 경우) 쿼리 재 컴파일 시간이 상당히 복잡 할 수 있습니다! 소스를 직접 확인하면 parent._recompileRequired = () => true;IEnumerable <T> 매개 변수를 포함하는 모든 쿼리에서 발생하는 것을 확인할 수 있습니다 . 우우!
jocull

답변:


66

업데이트 : EF6에 InExpression이 추가됨에 따라 Enumerable.Contains 처리 성능이 크게 향상되었습니다. 이 답변에 설명 된 접근 방식은 더 이상 필요하지 않습니다.

대부분의 시간이 쿼리 번역을 처리하는 데 소비된다는 것이 맞습니다. EF의 공급자 모델에는 현재 IN 절을 나타내는식이 포함되어 있지 않으므로 ADO.NET 공급자는 기본적으로 IN을 지원할 수 없습니다. 대신 Enumerable.Contains 구현은이를 OR 표현식 트리로 변환합니다. 즉, C #에서 다음과 같은 것입니다.

new []{1, 2, 3, 4}.Contains(i)

... 다음과 같이 표현할 수있는 DbExpression 트리를 생성합니다.

((1 = @i) OR (2 = @i)) OR ((3 = @i) OR (4 = @i))

(표현식 트리는 균형을 이루어야합니다. 왜냐하면 우리가 하나의 긴 척추에 대한 모든 OR이 있다면 표현식 방문자가 스택 오버플로를 칠 가능성이 더 많기 때문입니다 (예, 실제로 테스트에서이 작업을 수행했습니다)).

나중에 이와 같은 트리를 ADO.NET 공급자에게 보냅니다. ADO.NET 공급자는이 패턴을 인식하고 SQL 생성 중에 IN 절로 줄일 수 있습니다.

EF4에서 Enumerable.Contains에 대한 지원을 추가했을 때 공급자 모델에서 IN 표현식에 대한 지원을 도입하지 않고 수행하는 것이 바람직하다고 생각했으며 솔직히 10,000은 고객이 전달할 것으로 예상되는 요소 수보다 훨씬 많습니다. Enumerable.Contains. 즉, 이것이 성가신 일이며 표현식 트리를 조작하면 특정 시나리오에서 너무 비싸다는 것을 이해합니다.

개발자 중 한 명과이 문제를 논의했으며 앞으로 IN에 대한 최고 수준의 지원을 추가하여 구현을 변경할 수 있다고 믿습니다. 나는 이것이 우리의 백 로그에 추가되도록 할 것이지만, 우리가 만들고 싶은 다른 많은 개선이 있기 때문에 그것이 언제 만들어 질지 약속 할 수 없습니다.

스레드에서 이미 제안 된 해결 방법에 다음을 추가합니다.

포함에 전달하는 요소 수와 데이터베이스 왕복 수의 균형을 맞추는 메서드를 만드는 것이 좋습니다. 예를 들어, 필자의 테스트에서 SQL Server의 로컬 인스턴스에 대해 계산하고 실행하는 데 100 개의 요소가있는 쿼리가 1/60 초가 걸리는 것을 관찰했습니다. 100 개의 서로 다른 ID 세트로 100 개의 쿼리를 실행하면 10,000 개의 요소가있는 쿼리와 동일한 결과를 얻을 수있는 방식으로 쿼리를 작성할 수 있다면 18 초가 아닌 약 1.67 초 내에 결과를 얻을 수 있습니다.

다른 청크 크기는 쿼리 및 데이터베이스 연결 대기 시간에 따라 더 잘 작동합니다. 특정 쿼리의 경우, 즉 전달 된 시퀀스에 중복이 있거나 Enumerable.Contains가 중첩 조건에서 사용되는 경우 결과에서 중복 요소를 얻을 수 있습니다.

다음은 코드 스 니펫입니다 (입력을 청크로 분할하는 데 사용 된 코드가 너무 복잡해 보이면 죄송합니다. 동일한 작업을 수행하는 더 간단한 방법이 있지만 시퀀스에 대한 스트리밍을 유지하는 패턴을 찾으려고했습니다. LINQ에서 이와 비슷한 것을 찾을 수 없었으므로 아마도 그 부분을 과장했습니다. :)) :

용법:

var list = context.GetMainItems(ids).ToList();

컨텍스트 또는 저장소에 대한 방법 :

public partial class ContainsTestEntities
{
    public IEnumerable<Main> GetMainItems(IEnumerable<int> ids, int chunkSize = 100)
    {
        foreach (var chunk in ids.Chunk(chunkSize))
        {
            var q = this.MainItems.Where(a => chunk.Contains(a.Id));
            foreach (var item in q)
            {
                yield return item;
            }
        }
    }
}

열거 가능한 시퀀스를 분할하기위한 확장 메서드 :

public static class EnumerableSlicing
{

    private class Status
    {
        public bool EndOfSequence;
    }

    private static IEnumerable<T> TakeOnEnumerator<T>(IEnumerator<T> enumerator, int count, 
        Status status)
    {
        while (--count > 0 && (enumerator.MoveNext() || !(status.EndOfSequence = true)))
        {
            yield return enumerator.Current;
        }
    }

    public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> items, int chunkSize)
    {
        if (chunkSize < 1)
        {
            throw new ArgumentException("Chunks should not be smaller than 1 element");
        }
        var status = new Status { EndOfSequence = false };
        using (var enumerator = items.GetEnumerator())
        {
            while (!status.EndOfSequence)
            {
                yield return TakeOnEnumerator(enumerator, chunkSize, status);
            }
        }
    }
}

도움이 되었기를 바랍니다!


!(status.EndOfSequence = true)TakeOnEnumerator <T> 메서드에서 설명하려면 : 따라서이 식 할당의 부작용은 항상! true이므로 전체 식에 영향을주지 않습니다. 기본적 으로 가져올 항목이 남아 있지만 열거가 끝났을 때만 표시 stats.EndOfSequence됩니다 true.
arviman

아마도 Enumerable.Contains이전 버전의 EF에 비해 EF 6에서 처리 성능이 크게 향상 되었을 수 있습니다. 그러나 안타깝게도 사용 사례에서 여전히 만족 스럽거나 생산 준비가되어 있지는 않습니다.
Nik

24

방해가되는 성능 문제를 발견 한 경우 문제를 해결하는 데 오랜 시간을 소비하지 마십시오. 성공하지 못할 가능성이 높고 MS와 직접 통신해야하기 때문입니다 (프리미엄 지원이있는 경우). 나이.

성능 문제가있는 경우 해결 방법 및 해결 방법을 사용하고 EF는 직접 SQL을 의미합니다. 그것에 대해 나쁜 것은 없습니다. EF를 사용하는 것이 더 이상 SQL을 사용하지 않는 것은 거짓말이라는 글로벌 아이디어. SQL Server 2008 R2가 있으므로 :

  • ID를 전달하기 위해 테이블 ​​값 매개 변수를 허용하는 저장 프로 시저 만들기
  • 저장 프로 시저가 여러 결과 집합을 반환하도록 Include하여 최적의 방식으로 논리 를 에뮬레이션합니다.
  • 복잡한 쿼리 작성이 필요한 경우 저장 프로 시저 내에서 동적 SQL을 사용하십시오.
  • 사용 SqlDataReader결과를 얻을 수 있도록하고 엔티티를 생성
  • 컨텍스트에 연결하고 EF에서로드 된 것처럼 작업

성능이 중요하다면 더 나은 솔루션을 찾지 못할 것입니다. 현재 버전은 테이블 값 매개 변수 또는 여러 결과 집합을 지원하지 않으므로이 프로시 저는 EF에서 매핑 및 실행할 수 없습니다.


@Laddislav Mrnka list.Contains ()로 인해 유사한 성능 문제가 발생했습니다. ID를 전달하여 프로 시저를 만들어 보겠습니다. EF를 통해이 절차를 실행하면 성능 저하가 발생합니까?
Kurubaran 2015

9

중간 테이블을 추가하고 Contains 절을 사용해야하는 LINQ 쿼리에서 해당 테이블에 조인하여 EF Contains 문제를 해결할 수있었습니다. 이 접근 방식으로 놀라운 결과를 얻을 수있었습니다. 큰 EF 모델이 있고 EF 쿼리를 사전 컴파일 할 때 "Contains"가 허용되지 않으므로 "Contains"절을 사용하는 쿼리의 성능이 매우 저하되었습니다.

개요 :

  • SQL 서버에서 테이블을 생성 - 예를 들어 HelperForContainsOfIntTypeHelperIDGuid데이터 타입 ReferenceIDint데이터 유형 컬럼. 필요에 따라 데이터 유형이 다른 ReferenceID를 사용하여 다른 테이블을 만듭니다.

  • HelperForContainsOfIntTypeEF 모델에서 Entity / EntitySet 및 기타 이러한 테이블을 만듭니다 . 필요에 따라 다른 데이터 유형에 대해 다른 Entity / EntitySet을 만듭니다.

  • 의 입력을 받아 .NET을 IEnumerable<int>반환하는 .NET 코드에서 도우미 메서드를 만듭니다 Guid. 이 방법은 새롭게 생성 Guid및 삽입의 값 IEnumerable<int>에을 HelperForContainsOfIntType생성 함께 Guid. 다음으로, 메서드는 새로 생성 된 이것을 Guid호출자에게 반환 합니다. HelperForContainsOfIntType테이블에 빠르게 삽입 하려면 값 목록을 입력하고 삽입하는 저장 프로 시저를 만듭니다. SQL Server 2008 (ADO.NET)의 테이블 반환 매개 변수를 참조하십시오 . 서로 다른 데이터 유형에 대해 서로 다른 도우미를 만들거나 다른 데이터 유형을 처리하는 일반 도우미 메서드를 만듭니다.

  • 아래와 비슷한 EF 컴파일 된 쿼리를 만듭니다.

    static Func<MyEntities, Guid, IEnumerable<Customer>> _selectCustomers =
        CompiledQuery.Compile(
            (MyEntities db, Guid containsHelperID) =>
                from cust in db.Customers
                join x in db.HelperForContainsOfIntType on cust.CustomerID equals x.ReferenceID where x.HelperID == containsHelperID
                select cust 
        );
    
  • Contains절 에서 사용할 값으로 도우미 메서드를 호출하고 Guid쿼리에서 사용할를 가져옵니다 . 예를 들면 :

    var containsHelperID = dbHelper.InsertIntoHelperForContainsOfIntType(new int[] { 1, 2, 3 });
    var result = _selectCustomers(_dbContext, containsHelperID).ToList();
    

감사합니다! 내 문제를 해결하기 위해 다양한 솔루션을 사용했습니다.
Mike

5

내 원래 답변 편집-엔터티의 복잡성에 따라 가능한 해결 방법이 있습니다. EF가 엔터티를 채우기 위해 생성하는 SQL을 알고있는 경우 DbContext.Database.SqlQuery를 사용하여 직접 실행할 수 있습니다 . EF 4에서는 ObjectContext.ExecuteStoreQuery를 사용할 수 있다고 생각 하지만 시도하지 않았습니다.

예를 들어 아래의 원래 답변의 코드를 사용하여를 사용하여 SQL 문을 생성 StringBuilder하면 다음을 수행 할 수 있습니다

var rows = db.Database.SqlQuery<Main>(sql).ToArray();

총 시간은 약 26 초에서 0.5 초로 늘어났습니다.

나는 그것이 추악하다고 처음으로 말할 것이며, 더 나은 해결책이 제시되기를 바랍니다.

최신 정보

조금 더 생각한 끝에 조인을 사용하여 결과를 필터링하면 EF가 긴 ID 목록을 작성할 필요가 없다는 것을 깨달았습니다. 이것은 동시 쿼리 수에 따라 복잡 할 수 있지만 사용자 ID 또는 세션 ID를 사용하여 격리 할 수 ​​있다고 생각합니다.

이를 테스트하기 위해, 내가 만든 Target같은 스키마와 테이블을 Main. 그런 다음 a StringBuilder를 사용 INSERT하여 Target테이블을 1,000 개 단위 로 채우는 명령 을 만들었습니다 INSERT. SQL 문을 직접 실행하는 것이 EF (약 0.3 초 ​​대 2.5 초)를 거치는 것보다 훨씬 빠르며 테이블 스키마가 변경되지 않아야하므로 괜찮을 것이라고 생각합니다.

마지막으로를 사용하여 선택하면 join쿼리가 훨씬 간단 해지고 0.5 초 이내에 실행됩니다.

ExecuteStoreCommand("DELETE Target");

var ids = Main.Select(a => a.Id).ToArray();
var sb = new StringBuilder();

for (int i = 0; i < 10; i++)
{
    sb.Append("INSERT INTO Target(Id) VALUES (");
    for (int j = 1; j <= 1000; j++)
    {
        if (j > 1)
        {
            sb.Append(",(");
        }
        sb.Append(i * 1000 + j);
        sb.Append(")");
    }
    ExecuteStoreCommand(sb.ToString());
    sb.Clear();
}

var rows = (from m in Main
            join t in Target on m.Id equals t.Id
            select m).ToArray();

rows.Length.Dump();

조인을 위해 EF에서 생성 한 SQL :

SELECT 
[Extent1].[Id] AS [Id]
FROM  [dbo].[Main] AS [Extent1]
INNER JOIN [dbo].[Target] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Id]

(원래 답변)

이것은 대답이 아니지만 몇 가지 추가 정보를 공유하고 싶었고 의견에 맞추기에는 너무 깁니다. 결과를 재현 할 수 있었고 추가 할 몇 가지 다른 사항이 있습니다.

SQL 프로파일 러는 첫 번째 쿼리 ( Main.Select)와 두 번째 쿼리 실행 사이에 지연이 있음을 보여 Main.Where주므로 문제가 해당 크기 (48,980 바이트)의 쿼리를 생성하고 전송하는 데 있다고 의심했습니다.

그러나 T-SQL에서 같은 SQL 문을 구축 동적으로 일초보다 적게 소요되며 복용 ids하여에서 Main.Select동일한 SQL 문을 작성하고 사용을 실행, 문 SqlCommand콘솔에 내용을 작성하는 시간을 포함하여 사용자들은 0.112 초 갔고, .

이 시점에서 EF는 ids쿼리를 작성할 때 10,000 개 각각에 대해 분석 / 처리를 수행하고 있다고 생각 합니다. 확실한 대답과 해결책을 제공 할 수 있기를 바랍니다.

SSMS 및 LINQPad에서 시도한 코드는 다음과 같습니다 (너무 가혹하게 비판하지 마십시오. 퇴근하려고 서두르고 있습니다).

declare @sql nvarchar(max)

set @sql = 'SELECT 
[Extent1].[Id] AS [Id]
FROM [dbo].[Main] AS [Extent1]
WHERE [Extent1].[Id] IN ('

declare @count int = 0
while @count < 10000
begin
    if @count > 0 set @sql = @sql + ','
    set @count = @count + 1
    set @sql = @sql + cast(@count as nvarchar)
end
set @sql = @sql + ')'

exec(@sql)

var ids = Mains.Select(a => a.Id).ToArray();

var sb = new StringBuilder();
sb.Append("SELECT [Extent1].[Id] AS [Id] FROM [dbo].[Main] AS [Extent1] WHERE [Extent1].[Id] IN (");
for(int i = 0; i < ids.Length; i++)
{
    if (i > 0) 
        sb.Append(",");     
    sb.Append(ids[i].ToString());
}
sb.Append(")");

using (SqlConnection connection = new SqlConnection("server = localhost;database = Test;integrated security = true"))
using (SqlCommand command = connection.CreateCommand())
{
    command.CommandText = sb.ToString();
    connection.Open();
    using(SqlDataReader reader = command.ExecuteReader())
    {
        while(reader.Read())
        {
            Console.WriteLine(reader.GetInt32(0));
        }
    }
}

이것에 대한 귀하의 작업에 감사드립니다. 당신이 그것을 재현 할 수 있다는 것을 알면 기분이 나아집니다. 적어도 나는 미쳤지 않습니다! 불행히도 귀하의 해결 방법은 제 경우에는 실제로 도움이되지 않습니다. 짐작할 수 있듯이 여기에 제공된 예제는 문제를 격리하기 위해 최대한 단순화 되었기 때문입니다. 내 실제 쿼리에는 상당히 복잡한 스키마, .Include ()가 여러 다른 테이블에 있고 몇 가지 다른 LINQ 연산자도 포함됩니다.
마이크

@Mike, 복잡한 엔티티에 대해 작동하는 또 다른 아이디어를 추가했습니다. 다른 선택의 여지가 없다면 구현하는 것이 너무 어렵지 않을 것입니다.
제프 오가타

몇 가지 테스트를 수행했으며 SQL이 실행되기 전에 지연이 SQL을 생성하는 것이 맞다고 생각합니다. 세부 사항으로 내 질문을 업데이트했습니다.
Mike

@Mike, ID에 가입 할 수 있었습니까 (내 답변의 업데이트 참조)?
Jeff Ogata

성능 문제를 해결하기 위해 다양한 접근 방식을 사용했습니다. 꽤 못 생겼지 만 Microsoft가이 문제를 해결하기 전까지는 아마도 최선의 선택 일 것입니다.
Mike

5

Entity Framework에 익숙하지 않지만 다음을 수행하면 성능이 더 좋습니까?

대신 :

var ids = Main.Select(a => a.Id).ToArray();
var rows = Main.Where (a => ids.Contains(a.Id)).ToArray();

이것에 대해 (ID가 int라고 가정) :

var ids = new HashSet<int>(Main.Select(a => a.Id));
var rows = Main.Where (a => ids.Contains(a.Id)).ToArray();

나는 :) 왜, 어떻게하지만이 마법처럼 일 것하지 않는 매우 :) 감사
와히드 비 타르

1
성능이 더 좋은 이유에 대한 설명은 int []. Contains 호출이 첫 번째 호출에서 O (n) (잠재적으로 전체 배열 스캔) 인 반면 HashSet <int> .Contains 호출은 O (1)입니다. 해시 셋 성능 은 stackoverflow.com/questions/9812020/… 을 참조하십시오 .
Shiv

3
@ Shiv 나는 그것이 옳다고 믿지 않는다. EF는 모든 컬렉션을 가져와 SQL로 변환합니다. 수집 유형은 문제가되지 않아야합니다.
Rob

@Rob 나는 회의적입니다-그렇다면 성능 차이를 설명하기 위해 손실을 입었습니다. 그것이 무엇을했는지보기 위해 바이너리를 분석해야 할 수도 있습니다.
Shiv

1
HashSet은 IEnumerable이 아닙니다. LINQ에서 .Contains를 호출하는 IEnumerables는 성능이 저조합니다 (적어도 EF6 이전)
Jason Beck


2

포함에 대한 캐시 가능한 대안?

이것은 나를 물었으므로 Entity Framework 기능 제안 링크에 두 펜스를 추가했습니다.

문제는 분명히 SQL을 생성 할 때입니다. 쿼리 생성은 4 초 였지만 실행은 0.1 초였습니다.

동적 LINQ 및 OR를 사용할 때 SQL 생성에 시간이 오래 걸리지 만 캐시 할 수있는 무언가가 생성 된다는 것을 알았습니다 . 그래서 다시 실행할 때 0.2 초로 줄었습니다.

SQL in은 여전히 ​​생성되었습니다.

초기 히트를 감당할 수 있다면 고려해야 할 다른 사항이 있습니다. 배열 수는 많이 변경되지 않으며 쿼리를 많이 실행합니다. (LINQ Pad에서 테스트 됨)


또한 codeplex 사이트에서 투표하십시오. entityframework.codeplex.com/workitem/245 >
Dave

2

문제는 Entity Framework의 SQL 생성에 있습니다. 매개 변수 중 하나가 목록이면 쿼리를 캐시 할 수 없습니다.

EF가 쿼리를 캐시하도록하려면 목록을 문자열로 변환하고 문자열에 .Contains를 수행 할 수 있습니다.

예를 들어이 코드는 EF가 쿼리를 캐시 할 수 있기 때문에 훨씬 빠르게 실행됩니다.

var ids = Main.Select(a => a.Id).ToArray();
var idsString = "|" + String.Join("|", ids) + "|";
var rows = Main
.Where (a => idsString.Contains("|" + a.Id + "|"))
.ToArray();

이 쿼리가 생성되면 In 대신 Like를 사용하여 생성 될 가능성이 높으므로 C # 속도가 빨라지지만 잠재적으로 SQL 속도가 느려질 수 있습니다. 제 경우에는 SQL 실행에서 성능 저하가 발견되지 않았고 C #이 훨씬 빠르게 실행되었습니다.


1
좋은 생각이지만 이것은 해당 열의 인덱스를 사용하지 않습니다.
지출 자

예, 사실입니다. 이것이 SQL 실행 속도를 늦출 수 있다고 언급 한 이유입니다. 술어 빌더를 사용할 수없고 색인을 사용하지 않을 여유가있을만큼 충분히 작은 데이터 세트로 작업하는 경우 이것이 잠재적 인 대안이라고 생각합니다. 또한 술어 빌더가 선호되는 옵션이라고 언급 했어야한다고 생각합니다
user2704238

1
놀라운 솔루션입니다. 프로덕션 쿼리 런타임을 ~ 12,600 밀리 초에서 ~ 18 밀리 초로 늘 렸습니다. 이것은 엄청난 개선입니다. 대단히 감사합니다 !!!
야곱
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.