Tại sao toán tử Contains () làm giảm hiệu suất của Entity Framework một cách đáng kể như vậy?


79

CẬP NHẬT 3: Theo thông báo này , điều này đã được nhóm EF giải quyết trong EF6 alpha 2.

CẬP NHẬT 2: Tôi đã tạo một đề xuất để khắc phục sự cố này. Để bỏ phiếu cho nó, hãy vào đây .

Hãy xem xét một cơ sở dữ liệu SQL với một bảng rất đơn giản.

CREATE TABLE Main (Id INT PRIMARY KEY)

Tôi điền vào bảng với 10.000 bản ghi.

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)

Tôi tạo một mô hình EF cho bảng và chạy truy vấn sau trong LINQPad (Tôi đang sử dụng chế độ "Câu lệnh C #" nên LINQPad không tự động tạo kết xuất).

var rows = 
  Main
  .ToArray();

Thời gian thực hiện ~ 0,07 giây. Bây giờ tôi thêm toán tử Chứa và chạy lại truy vấn.

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

Thời gian thực hiện cho trường hợp này là 20,14 giây (chậm hơn 288 lần)!

Lúc đầu, tôi nghi ngờ rằng T-SQL được phát ra cho truy vấn mất nhiều thời gian hơn để thực thi, vì vậy tôi đã thử cắt và dán nó từ ngăn SQL của LINQPad vào 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,...

Và kết quả là

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

Tiếp theo, tôi nghi ngờ LINQPad gây ra sự cố, nhưng hiệu suất vẫn như nhau cho dù tôi chạy nó trong LINQPad hay trong một ứng dụng bảng điều khiển.

Vì vậy, có vẻ như vấn đề nằm ở đâu đó trong Entity Framework.

Tôi đang làm gì đó sai ở đây? Đây là phần quan trọng về thời gian trong mã của tôi, vậy tôi có thể làm gì để tăng tốc hiệu suất không?

Tôi đang sử dụng Entity Framework 4.1 và Sql Server 2008 R2.

CẬP NHẬT 1:

Trong cuộc thảo luận bên dưới, có một số câu hỏi về việc liệu sự chậm trễ xảy ra trong khi EF đang tạo truy vấn ban đầu hay trong khi phân tích dữ liệu mà nó nhận được trở lại. Để kiểm tra điều này, tôi đã chạy đoạn mã sau,

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

buộc EF phải tạo truy vấn mà không cần thực thi nó đối với cơ sở dữ liệu. Kết quả là mã này yêu cầu ~ 20 giây để chạy, vì vậy có vẻ như gần như toàn bộ thời gian được dành cho việc xây dựng truy vấn ban đầu.

Sau đó, CompiledQuery để giải cứu? Không quá nhanh ... CompiledQuery yêu cầu các tham số được truyền vào truy vấn phải là các kiểu cơ bản (int, string, float, v.v.). Nó sẽ không chấp nhận mảng hoặc IEnumerable, vì vậy tôi không thể sử dụng nó cho danh sách Id.


1
Bạn đã thử var qry = Main.Where (a => ids.Contains(a.Id)); var rows = qry.ToArray();xem phần nào của truy vấn đang chiếm thời gian chưa?
Andrew Cooper

nó không phải là EF làm suy giảm truy vấn của bạn, nó là truy vấn thực tế mà bạn đang cố gắng chạy; bạn có thể giải thích những gì bạn đang cố gắng làm? có lẽ có một cách tiếp cận tốt hơn với nhu cầu của bạn
Kris Ivanov

@AndrewCooper Tôi vừa mới thử nó và do thực thi hoãn lại nên câu lệnh đầu tiên (không có ToArray) thực thi gần như ngay lập tức. Truy vấn, bao gồm cả bộ lọc Chứa, không thực sự chạy cho đến khi bạn thực thi ToArray ().
Mike

5
Chỉ và cập nhật về điều này: EF6 alpha 2 bao gồm một cải tiến giúp tăng tốc quá trình dịch Enumerable.Contains. Xem thông báo tại đây: blog.msdn.com/b/adonet/archive/2012/12/10/… . Các thử nghiệm của riêng tôi cho thấy rằng việc dịch list.Contains (x) cho một danh sách có 100.000 phần tử int giờ đây chỉ mất chưa đầy một giây và thời gian tăng lên xấp xỉ tuyến tính với số phần tử trong danh sách. Cảm ơn phản hồi của bạn và giúp chúng tôi cải thiện EF!
lặn biển

1
Hãy cẩn thận với điều này ... các truy vấn với bất kỳ tham số IEnumerable nào không thể được lưu vào bộ nhớ cache, điều này có thể gây ra các tác dụng phụ khá nghiêm trọng khi kế hoạch truy vấn của bạn phức tạp. Nếu bạn phải chạy các hoạt động nhiều lần (ví dụ: sử dụng Chứa để lấy các khối dữ liệu), bạn có thể có một số lần biên dịch lại truy vấn khá khó chịu! Kiểm tra nguồn cho chính bạn và bạn có thể thấy điều đó parent._recompileRequired = () => true;xảy ra cho tất cả các truy vấn có chứa tham số IEnumerable <T>. Ụt!
jocull

Câu trả lời:


66

CẬP NHẬT: Với việc bổ sung InExpression trong EF6, hiệu suất xử lý Enumerable.Contains được cải thiện đáng kể. Cách tiếp cận được mô tả trong câu trả lời này không còn cần thiết nữa.

Bạn nói đúng rằng hầu hết thời gian được dành để xử lý bản dịch của truy vấn. Mô hình nhà cung cấp của EF hiện không bao gồm biểu thức đại diện cho mệnh đề IN, do đó, nhà cung cấp ADO.NET không thể hỗ trợ IN nguyên bản. Thay vào đó, việc triển khai Enumerable.Contains chuyển nó thành một cây biểu thức OR, tức là đối với một cái gì đó trong C # trông giống như thế này:

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

... chúng ta sẽ tạo một cây DbExpression có thể được biểu diễn như sau:

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

(Các cây biểu thức phải được cân bằng bởi vì nếu chúng ta có tất cả các OR trên một cột sống dài thì sẽ có nhiều khả năng khách truy cập biểu thức gặp phải lỗi tràn ngăn xếp (vâng, chúng tôi thực sự đã đạt được điều đó trong thử nghiệm của mình))

Sau đó, chúng tôi gửi một cây như thế này đến nhà cung cấp ADO.NET, nhà cung cấp này có thể có khả năng nhận ra mẫu này và giảm nó thành mệnh đề IN trong quá trình tạo SQL.

Khi chúng tôi thêm hỗ trợ cho Enumerable.Contains trong EF4, chúng tôi nghĩ rằng chúng tôi mong muốn làm điều đó mà không cần phải giới thiệu hỗ trợ cho các biểu thức IN trong mô hình nhà cung cấp và thành thật mà nói, 10.000 nhiều hơn nhiều so với số phần tử mà chúng tôi dự đoán khách hàng sẽ chuyển đến Enumerable.Contains. Điều đó nói rằng, tôi hiểu rằng đây là một sự khó chịu và rằng việc thao tác các cây biểu thức làm cho mọi thứ trở nên quá đắt trong kịch bản cụ thể của bạn.

Tôi đã thảo luận vấn đề này với một trong những nhà phát triển của chúng tôi và chúng tôi tin rằng trong tương lai, chúng tôi có thể thay đổi việc triển khai bằng cách thêm hỗ trợ hạng nhất cho IN. Tôi sẽ đảm bảo điều này sẽ được thêm vào công việc tồn đọng của chúng tôi, nhưng tôi không thể hứa khi nào nó sẽ hoàn thành vì có nhiều cải tiến khác mà chúng tôi muốn thực hiện.

Đối với các cách giải quyết đã được đề xuất trong chuỗi, tôi sẽ thêm những điều sau:

Hãy xem xét việc tạo một phương thức cân bằng số vòng quay cơ sở dữ liệu với số phần tử bạn chuyển đến Vùng chứa. Ví dụ: trong thử nghiệm của riêng tôi, tôi đã quan sát thấy rằng việc tính toán và thực thi đối với phiên bản cục bộ của SQL Server, truy vấn với 100 phần tử mất 1/60 giây. Nếu bạn có thể viết truy vấn của mình theo cách thực hiện 100 truy vấn với 100 bộ id khác nhau sẽ cho bạn kết quả tương đương với truy vấn có 10.000 phần tử, thì bạn có thể nhận được kết quả trong khoảng 1,67 giây thay vì 18 giây.

Các kích thước phân đoạn khác nhau sẽ hoạt động tốt hơn tùy thuộc vào truy vấn và độ trễ của kết nối cơ sở dữ liệu. Đối với một số truy vấn nhất định, tức là nếu chuỗi được truyền có trùng lặp hoặc nếu Enumerable.Contains được sử dụng trong điều kiện lồng nhau, bạn có thể nhận được các phần tử trùng lặp trong kết quả.

Đây là một đoạn mã (xin lỗi nếu đoạn mã được sử dụng để cắt dữ liệu đầu vào thành nhiều đoạn trông hơi phức tạp. Có nhiều cách đơn giản hơn để đạt được điều tương tự, nhưng tôi đã cố gắng tìm ra một mẫu duy trì sự phát trực tuyến cho chuỗi và Tôi không thể tìm thấy bất cứ điều gì giống như nó trong LINQ, vì vậy tôi có thể đã vượt quá phần đó :)):

Sử dụng:

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

Phương pháp cho ngữ cảnh hoặc kho lưu trữ:

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;
            }
        }
    }
}

Các phương pháp mở rộng để cắt các chuỗi có thể đếm được:

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);
            }
        }
    }
}

Hi vọng điêu nay co ich!


Để giải thích !(status.EndOfSequence = true)trong phương thức TakeOnEnumerator <T>: Vì vậy, tác dụng phụ của phép gán biểu thức này sẽ luôn là! True do đó không ảnh hưởng đến biểu thức tổng thể. Về cơ bản, nó đánh dấu stats.EndOfSequencetruechỉ khi có các mục còn lại để tìm nạp, nhưng bạn đã đạt đến cuối bảng liệt kê.
arviman

Có thể hiệu suất xử lý Enumerable.Containsđược cải thiện đáng kể trong EF 6 so với các phiên bản trước của EF. Nhưng, thật không may, nó vẫn chưa đạt yêu cầu / sẵn sàng sản xuất trong các trường hợp sử dụng của chúng tôi.
Nik

24

Nếu bạn tìm thấy một vấn đề hiệu suất đang cản trở bạn, đừng cố gắng dành nhiều thời gian để giải quyết nó bởi vì hầu hết bạn sẽ không thành công và bạn sẽ phải liên lạc trực tiếp với MS (nếu bạn có hỗ trợ cao cấp) và điều đó lứa tuổi.

Sử dụng cách giải quyết và cách giải quyết khác trong trường hợp có vấn đề về hiệu suất và EF có nghĩa là SQL trực tiếp. Không có gì xấu về nó. Ý tưởng toàn cầu rằng sử dụng EF = không sử dụng SQL nữa là một lời nói dối. Bạn có SQL Server 2008 R2 nên:

  • Tạo thủ tục được lưu trữ chấp nhận tham số có giá trị của bảng để chuyển id của bạn
  • Cho phép thủ tục được lưu trữ của bạn trả về nhiều tập kết quả để mô phỏng Includelogic theo cách tối ưu
  • Nếu bạn cần một số xây dựng truy vấn phức tạp, hãy sử dụng SQL động bên trong thủ tục được lưu trữ
  • Sử dụng SqlDataReaderđể nhận kết quả và xây dựng các thực thể của bạn
  • Gắn chúng vào ngữ cảnh và làm việc với chúng như thể chúng được tải từ EF

Nếu hiệu suất là quan trọng đối với bạn, bạn sẽ không tìm thấy giải pháp tốt hơn. EF không thể ánh xạ và thực thi quy trình này vì phiên bản hiện tại không hỗ trợ các tham số có giá trị bảng hoặc nhiều tập kết quả.


@Laddislav Mrnka Chúng tôi đã gặp sự cố hiệu suất tương tự do list.Contains (). Chúng tôi sẽ thử tạo các thủ tục bằng cách chuyển id. Chúng ta có gặp phải bất kỳ tác động nào về hiệu suất nếu chúng ta chạy quy trình này thông qua EF không?
Kurubaran

9

Chúng tôi đã có thể giải quyết vấn đề EF Contains bằng cách thêm một bảng trung gian và nối trên bảng đó từ truy vấn LINQ cần sử dụng mệnh đề Contains. Chúng tôi đã có thể nhận được kết quả đáng kinh ngạc với cách tiếp cận này. Chúng tôi có một mô hình EF lớn và vì "Chứa" không được phép khi biên dịch trước các truy vấn EF, chúng tôi đã nhận được hiệu suất rất kém đối với các truy vấn sử dụng mệnh đề "Chứa".

Một cái nhìn tổng quan:

  • Tạo một bảng trong SQL Server - ví dụ HelperForContainsOfIntTypevới HelperIDcác Guiddữ liệu kiểu và ReferenceIDcác intcột dữ liệu kiểu. Tạo các bảng khác nhau với ReferenceID của các kiểu dữ liệu khác nhau nếu cần.

  • Tạo một Entity / EntitySet cho HelperForContainsOfIntTypevà các bảng khác như vậy trong mô hình EF. Tạo Entity / EntitySet khác nhau cho các kiểu dữ liệu khác nhau nếu cần.

  • Tạo một phương thức trợ giúp trong mã .NET lấy đầu vào của một IEnumerable<int>và trả về một Guid. Phương thức này tạo mới Guidvà chèn các giá trị từ IEnumerable<int>vào HelperForContainsOfIntTypecùng với giá trị đã tạo Guid. Tiếp theo, phương thức này trả về cái mới được tạo Guidcho người gọi. Để chèn nhanh vào HelperForContainsOfIntTypebảng, hãy tạo một thủ tục được lưu trữ lấy đầu vào danh sách các giá trị và thực hiện việc chèn. Xem Tham số giá trị bảng trong SQL Server 2008 (ADO.NET) . Tạo các trình trợ giúp khác nhau cho các kiểu dữ liệu khác nhau hoặc tạo một phương pháp trợ giúp chung để xử lý các kiểu dữ liệu khác nhau.

  • Tạo một truy vấn đã biên dịch EF tương tự như sau:

    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 
        );
    
  • Gọi phương thức trợ giúp với các giá trị được sử dụng trong Containsmệnh đề và lấy giá trị Guidđể sử dụng trong truy vấn. Ví dụ:

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

Cám ơn vì cái này! Tôi đã sử dụng một biến thể của giải pháp của bạn để giải quyết vấn đề của tôi.
Mike

5

Chỉnh sửa câu trả lời ban đầu của tôi - Có thể có một giải pháp khác, tùy thuộc vào mức độ phức tạp của các thực thể của bạn. Nếu bạn biết sql mà EF tạo ra để điền các thực thể của mình, bạn có thể thực thi nó trực tiếp bằng cách sử dụng DbContext.Database.SqlQuery . Trong EF 4, tôi nghĩ bạn có thể sử dụng ObjectContext.ExecuteStoreQuery , nhưng tôi đã không thử.

Ví dụ: sử dụng mã từ câu trả lời ban đầu của tôi bên dưới để tạo câu lệnh sql bằng cách sử dụng a StringBuilder, tôi đã có thể thực hiện như sau

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

và tổng thời gian từ khoảng 26 giây đến 0,5 giây.

Tôi sẽ là người đầu tiên nói rằng nó xấu xí, và hy vọng một giải pháp tốt hơn sẽ xuất hiện.

cập nhật

Sau khi suy nghĩ kỹ hơn một chút, tôi nhận ra rằng nếu bạn sử dụng phép nối để lọc kết quả của mình, EF không phải tạo danh sách id dài như vậy. Điều này có thể phức tạp tùy thuộc vào số lượng truy vấn đồng thời, nhưng tôi tin rằng bạn có thể sử dụng id người dùng hoặc id phiên để cô lập chúng.

Để kiểm tra điều này, tôi đã tạo một Targetbảng có cùng một lược đồ như Main. Sau đó, tôi đã sử dụng a StringBuilderđể tạo INSERTcác lệnh để điền vào Targetbảng theo lô 1.000 vì đó là hầu hết SQL Server sẽ chấp nhận trong một lệnh duy nhất INSERT. Thực hiện trực tiếp các câu lệnh sql nhanh hơn nhiều so với thực hiện qua EF (khoảng 0,3 giây so với 2,5 giây) và tôi tin rằng sẽ ổn vì lược đồ bảng không nên thay đổi.

Cuối cùng, việc chọn bằng cách sử dụng joindẫn đến một truy vấn đơn giản hơn nhiều và được thực thi trong vòng chưa đầy 0,5 giây.

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();

Và sql do EF tạo cho phép nối:

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

(câu trả lời ban đầu)

Đây không phải là một câu trả lời, nhưng tôi muốn chia sẻ một số thông tin bổ sung và nó quá dài để đưa vào một bình luận. Tôi đã có thể tái tạo kết quả của bạn và có một số thứ khác cần bổ sung:

SQL Profiler cho thấy sự chậm trễ giữa việc thực thi truy vấn đầu tiên ( Main.Select) và Main.Wheretruy vấn thứ hai , vì vậy tôi nghi ngờ vấn đề là khi tạo và gửi truy vấn có kích thước đó (48,980 byte).

Tuy nhiên, việc tạo cùng một câu lệnh sql trong T-SQL động chỉ mất chưa đến 1 giây và lấy idstừ Main.Selectcâu lệnh của bạn , xây dựng cùng một câu lệnh sql và thực thi nó bằng cách SqlCommandmất 0,112 giây và đó là bao gồm cả thời gian để ghi nội dung vào bảng điều khiển .

Tại thời điểm này, tôi nghi ngờ rằng EF đang thực hiện một số phân tích / xử lý cho từng trong số 10.000 idskhi nó xây dựng truy vấn. Ước gì tôi có thể cung cấp một câu trả lời và giải pháp dứt điểm :(.

Đây là mã tôi đã thử trong SSMS và LINQPad (vui lòng không phê bình quá gay gắt, tôi đang vội cố gắng rời khỏi công việc):

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));
        }
    }
}

Cảm ơn bạn đã làm việc về điều này. Biết bạn có thể tái tạo nó khiến tôi cảm thấy tốt hơn - ít nhất là tôi không bị điên! Rất tiếc, cách giải quyết của bạn không thực sự hữu ích trong trường hợp của tôi vì như bạn có thể đoán, ví dụ tôi đưa ra ở đây đã được đơn giản hóa hết mức có thể để cô lập vấn đề. Truy vấn thực tế của tôi liên quan đến một lược đồ khá phức tạp, .Include () trên một số bảng khác và một vài toán tử LINQ khác.
Mike

@Mike, tôi đã thêm một ý tưởng khác sẽ hoạt động cho các thực thể phức tạp. Hy vọng rằng nó sẽ không quá khó để thực hiện nếu bạn không có lựa chọn nào khác.
Jeff Ogata,

Tôi đã thực hiện một số thử nghiệm và tôi nghĩ rằng bạn đã chính xác rằng sự chậm trễ trong việc tạo SQL trước khi nó được thực thi. Tôi đã cập nhật câu hỏi của mình với các chi tiết.
Mike

@Mike, bạn có thể thử tham gia vào id (xem cập nhật trong câu trả lời của tôi) không?
Jeff Ogata

Tôi kết thúc bằng cách sử dụng một biến thể của cách tiếp cận của bạn để giải quyết vấn đề hiệu suất. Nó có vẻ khá xấu, nhưng có lẽ là lựa chọn tốt nhất cho đến khi (và nếu) Microsoft giải quyết vấn đề này.
Mike

5

Tôi không quen thuộc với Entity Framework nhưng liệu hiệu suất có tốt hơn nếu bạn làm như sau không?

Thay vì điều này:

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

làm thế nào về điều này (giả sử ID là một int):

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

Tôi không lý do tại sao và làm thế nào nhưng nó đã làm việc như một nét duyên dáng :) Cảm ơn bạn rất nhiều :)
Wahid Bitar

1
Lời giải thích lý do tại sao hiệu suất tốt hơn là lệnh gọi int []. Chứa trong lần gọi đầu tiên là O (n) - có khả năng là quét toàn bộ - trong khi lệnh gọi HashSet <int> .Contains là O (1). Xem stackoverflow.com/questions/9812020/… để biết hiệu suất hashset.
Shiv

3
@Shiv Tôi không tin điều đó chính xác. EF sẽ lấy bất kỳ bộ sưu tập nào và dịch nó sang SQL. Loại bộ sưu tập không phải là một vấn đề.
Rob

@Rob Tôi hoài nghi - thật khó để giải thích sự khác biệt về hiệu suất nếu đúng như vậy. Có thể phải phân tích nhị phân để xem nó đã làm gì.
Shiv

1
HashSet không phải là IEnumerable. Cuộc gọi IEnumerables. Các nội dung trong LINQ hoạt động kém (ít nhất là trước EF6.)
Jason Beck


2

Một giải pháp thay thế có thể lưu vào bộ nhớ cache cho Chứa?

Điều này chỉ khiến tôi khó chịu vì vậy tôi đã thêm hai pence của mình vào liên kết Đề xuất tính năng khung thực thể.

Vấn đề chắc chắn là khi tạo SQL. Tôi có một khách hàng về dữ liệu của người mà quá trình tạo truy vấn là 4 giây nhưng thời gian thực thi là 0,1 giây.

Tôi nhận thấy rằng khi sử dụng LINQ và ORs động , quá trình tạo sql mất nhiều thời gian nhưng nó tạo ra thứ gì đó có thể được lưu vào bộ nhớ đệm . Vì vậy, khi thực hiện lại nó giảm xuống còn 0,2 giây.

Lưu ý rằng một SQL trong vẫn được tạo.

Chỉ cần một số thứ khác cần xem xét nếu bạn có thể đạt được lần truy cập đầu tiên, số lượng mảng của bạn không thay đổi nhiều và chạy truy vấn rất nhiều. (Đã thử nghiệm trong LINQ Pad)


Cũng bỏ phiếu cho nó trên trang web CodePlex < entityframework.codeplex.com/workitem/245 >
Dave

2

Vấn đề là với việc tạo SQL của Entity Framework. Nó không thể cache truy vấn nếu một trong các tham số là danh sách.

Để lấy EF vào bộ nhớ cache truy vấn của bạn, bạn có thể chuyển đổi danh sách của mình thành một chuỗi và thực hiện một .Contains trên chuỗi.

Vì vậy, ví dụ: mã này sẽ chạy nhanh hơn nhiều vì EF có thể lưu vào bộ nhớ cache truy vấn:

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

Khi truy vấn này được tạo, nó có thể sẽ được tạo bằng Like thay vì In, do đó nó sẽ tăng tốc C # của bạn nhưng nó có thể làm chậm SQL của bạn. Trong trường hợp của tôi, tôi không nhận thấy bất kỳ sự giảm hiệu suất nào trong quá trình thực thi SQL của mình và C # chạy nhanh hơn đáng kể.


1
Ý tưởng hay, nhưng điều này sẽ không sử dụng bất kỳ chỉ mục nào trên cột được đề cập.
tiêu

Vâng, đó là sự thật, đó là lý do tại sao tôi đã đề cập rằng nó có thể làm chậm quá trình thực thi SQL. Tôi đoán đây chỉ là một giải pháp thay thế tiềm năng nếu bạn không thể sử dụng trình tạo vị từ và bạn đang làm việc với tập dữ liệu đủ nhỏ để bạn có thể đủ khả năng không sử dụng chỉ mục. Tôi cũng cho rằng lẽ ra tôi nên đề cập rằng trình tạo vị từ là tùy chọn ưu tiên
user2704238

1
Thật là một giải pháp TUYỆT VỜI. Chúng tôi đã quản lý để tăng thời gian chạy truy vấn sản xuất của mình từ ~ 12.600 mili giây lên chỉ ~ 18 mili giây. Đây là cải tiến LỚN. Cảm ơn rât nhiều !!!
Jacob
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.