2012-02-08 23 views
19

Khuôn khổ thực thể luôn sử dụng các hằng số trong SQL được tạo cho các giá trị được cung cấp cho Skip()Take().Khung thực thể Force để sử dụng tham số SQL để sử dụng lại bộ đệm SQL proc tốt hơn

Trong ví dụ cực kỳ đơn giản dưới đây:

int x = 10; 
int y = 10; 

var stuff = context.Users 
    .OrderBy(u => u.Id) 
    .Skip(x) 
    .Take(y) 
    .Select(u => u.Id) 
    .ToList(); 

x = 20; 

var stuff2 = context.Users 
    .OrderBy(u => u.Id) 
    .Skip(x) 
    .Take(y) 
    .Select(u => u.Id) 
    .ToList(); 

mã trên tạo ra các truy vấn SQL sau:

SELECT TOP (10) 
[Extent1].[Id] AS [Id] 
FROM (SELECT [Extent1].[Id] AS [Id], row_number() OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number] 
    FROM [dbo].[User] AS [Extent1] 
) AS [Extent1] 
WHERE [Extent1].[row_number] > 10 
ORDER BY [Extent1].[Id] ASC 

SELECT TOP (10) 
[Extent1].[Id] AS [Id] 
FROM (SELECT [Extent1].[Id] AS [Id], row_number() OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number] 
    FROM [dbo].[User] AS [Extent1] 
) AS [Extent1] 
WHERE [Extent1].[row_number] > 20 
ORDER BY [Extent1].[Id] ASC 

Kết quả trong 2 kế hoạch Adhoc thêm vào bộ nhớ cache SQL proc với 1 sử dụng mỗi .

Những gì tôi muốn thực hiện là để parameterize logic Skip()Take() nên các truy vấn SQL sau đây được tạo ra:

EXEC sp_executesql N'SELECT TOP (@p__linq__0) 
[Extent1].[Id] AS [Id] 
FROM (SELECT [Extent1].[Id] AS [Id], row_number() OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number] 
    FROM [dbo].[User] AS [Extent1] 
) AS [Extent1] 
WHERE [Extent1].[row_number] > @p__linq__1 
ORDER BY [Extent1].[Id] ASC',N'@p__linq__0 int,@p__linq__1 int',@p__linq__0=10,@p__linq__1=10 

EXEC sp_executesql N'SELECT TOP (@p__linq__0) 
[Extent1].[Id] AS [Id] 
FROM (SELECT [Extent1].[Id] AS [Id], row_number() OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number] 
    FROM [dbo].[User] AS [Extent1] 
) AS [Extent1] 
WHERE [Extent1].[row_number] > @p__linq__1 
ORDER BY [Extent1].[Id] ASC',N'@p__linq__0 int,@p__linq__1 int',@p__linq__0=10,@p__linq__1=20 

Điều này dẫn đến 1 kế hoạch chuẩn bị bổ sung vào bộ nhớ cache SQL proc với 2 mục đích sử dụng.

Tôi có một số truy vấn khá phức tạp và đang gặp phải chi phí đáng kể (ở phía máy chủ SQL) trong lần chạy đầu tiên và thực thi nhanh hơn trên các lần chạy tiếp theo (vì nó có thể sử dụng bộ đệm kế hoạch). Lưu ý rằng các truy vấn nâng cao hơn này đã sử dụng sp_executesql vì các giá trị khác được tham số hóa nên tôi không quan tâm đến khía cạnh đó.

Bộ truy vấn đầu tiên được tạo ở trên về cơ bản có nghĩa là bất kỳ logic phân trang nào sẽ tạo mục nhập mới trong bộ đệm kế hoạch cho từng trang, làm đầy bộ nhớ cache và yêu cầu phát sinh chi phí phát sinh cho từng trang.

Tôi có thể buộc Khung thực thể tham số hóa các giá trị không? Tôi đã nhận thấy các giá trị khác, ví dụ: trong mệnh đề Where, đôi khi nó tham số hóa giá trị và đôi khi nó sử dụng hằng số.

Tôi có hoàn toàn không dùng bữa trưa không? Có lý do nào khiến hành vi hiện tại của Entity Framework tốt hơn hành vi mà tôi mong muốn không?

Chỉnh sửa: Trong trường hợp có liên quan, tôi nên đề cập rằng tôi đang sử dụng Khuôn khổ thực thể 4.2.

Chỉnh sửa 2: Câu hỏi này không phải là một bản sao của Entity Framework/Linq to SQL: Skip & Take, mà chỉ đơn thuần là hỏi làm thế nào để đảm bảo rằng SkipTake thực hiện trong SQL thay vì trên máy khách. Câu hỏi này liên quan đến việc tham số hóa các giá trị này.

+0

Liên kết này giải thích cách bạn có thể sử dụng LINQ với SQL Params bạn sẽ phải cuộn xuống cuối liên kết để xem giải thích và ví dụ trang LinqPad - http://www.linqpad.net/WhyLINQBeatsSQL.aspx – MethodMan

+2

Tuyệt vời quan sát. Tôi thường không sử dụng EF cho các dự án "thực", chỉ chơi đùa với những thứ nhỏ nhặt, và không bao giờ nhận thấy hành vi này trước đây. Nếu EF không tham số hóa mọi thứ có thể, thì tôi cho rằng một lỗ hổng lớn. – CodingWithSpike

+1

câu hỏi hay - bạn nghĩ rằng họ đã tối ưu hóa cho việc tái sử dụng kế hoạch có thể là – BrokenGlass

Trả lời

24

Cập nhật: phương thức Bỏ qua và tận dụng các tham số lambda được mô tả bên dưới là một phần của khung thực thể từ phiên bản 6 trở đi. Bạn có thể tận dụng chúng bằng cách nhập không gian tên System.Data.Entity vào mã của bạn.

Nói chung LINQ to Entities dịch các hằng số dưới dạng các hằng số và các biến được truyền cho truy vấn thành các tham số.

Vấn đề là phiên bản có thể truy vấn Bỏ qua và chấp nhận tham số nguyên đơn giản chứ không phải biểu thức lambda, do đó LINQ to Entities có thể thấy giá trị bạn vượt qua, không thể thấy thực tế bạn đã sử dụng biến để chuyển chúng nói cách khác, các phương thức như Skip và Take không có quyền truy cập vào phần đóng của phương thức).

Điều này không chỉ ảnh hưởng đến tham số trong LINQ to Entities mà còn là kỳ vọng học được nếu bạn chuyển biến cho truy vấn LINQ giá trị mới nhất của biến được sử dụng mỗi khi bạn thực hiện lại truy vấn. Ví dụ, một cái gì đó như thế này làm việc cho đâu nhưng không phải cho Skip hoặc Chụp:

var letter = ""; 
var q = from db.Beattles.Where(p => p.Name.StartsWith(letter)); 

letter = "p"; 
var beattle1 = q.First(); // Returns Paul 

letter = "j"; 
var beattle2 = q.First(); // Returns John 

Lưu ý rằng tính đặc thù này cũng ảnh hưởng đến elementAt nhưng điều này hiện chưa được hỗ trợ bởi LINQ to Entities.

Đây là một thủ thuật mà bạn có thể sử dụng để buộc các tham số của Skip và Take và đồng thời làm cho họ cư xử giống như các nhà khai thác truy vấn khác:

public static class PagingExtensions 
{ 
    private static readonly MethodInfo SkipMethodInfo = 
     typeof(Queryable).GetMethod("Skip"); 

    public static IQueryable<TSource> Skip<TSource>(
     this IQueryable<TSource> source, 
     Expression<Func<int>> countAccessor) 
    { 
     return Parameterize(SkipMethodInfo, source, countAccessor); 
    } 

    private static readonly MethodInfo TakeMethodInfo = 
     typeof(Queryable).GetMethod("Take"); 

    public static IQueryable<TSource> Take<TSource>(
     this IQueryable<TSource> source, 
     Expression<Func<int>> countAccessor) 
    { 
     return Parameterize(TakeMethodInfo, source, countAccessor); 
    } 

    private static IQueryable<TSource> Parameterize<TSource, TParameter>(
     MethodInfo methodInfo, 
     IQueryable<TSource> source, 
     Expression<Func<TParameter>> parameterAccessor) 
    { 
     if (source == null) 
      throw new ArgumentNullException("source"); 
     if (parameterAccessor == null) 
      throw new ArgumentNullException("parameterAccessor"); 
     return source.Provider.CreateQuery<TSource>(
      Expression.Call(
       null, 
       methodInfo.MakeGenericMethod(new[] { typeof(TSource) }), 
       new[] { source.Expression, parameterAccessor.Body })); 
    } 
} 

Lớp trên định nghĩa quá tải mới của Skip và Đi mà mong đợi một biểu thức lambda và do đó có thể nắm bắt các biến. Sử dụng các phương pháp như thế này sẽ dẫn đến các biến được LINQ chuyển sang tham số:

int x = 10;  
int y = 10;  

var query = context.Users.OrderBy(u => u.Id).Skip(() => x).Take(() => y);  

var result1 = query.ToList(); 

x = 20; 

var result2 = query.ToList(); 

Hy vọng điều này sẽ hữu ích.

+1

Câu trả lời tuyệt vời. Tôi nghĩ thật tuyệt khi thấy các thành viên trong nhóm EF tham gia cộng đồng! – GWB

+0

Có vẻ như các phương pháp từ giải pháp này hiện được tích hợp sẵn cho EF6. :) – GWB

+0

Có :) Chúng tôi đã thêm chúng dưới dạng các phương pháp mở rộng cho IQueryable . Bạn chỉ cần đặt không gian tên System.Data.Entity vào phạm vi và có thể viết Skip và Take với tham số lambda. Tôi sẽ cập nhật câu trả lời. – divega

2

Các phương pháp SkipTop của ObjectQuery<T> có thể được tham số. Có một ví dụ tại MSDN.

tôi đã làm một điều tương tự trong một mô hình riêng và hồ sơ sql máy chủ của tôi cho thấy những phần

SELECT TOP (@limit) 

WHERE [Extent1].[row_number] > @skip 

Vì vậy, vâng. Nó có thể được thực hiện. Và tôi đồng ý với những người khác rằng đây là một quan sát có giá trị mà bạn đã thực hiện ở đây.

+0

Điều thú vị là họ đã thêm phương thức 'Top' thay vì sử dụng lại' Take'. Tôi đang bỏ qua/lấy một phép chiếu ẩn danh để truy vấn không thuộc loại 'ObjectQuery ' vì vậy tôi không chắc liệu tôi có thể sử dụng phương pháp này hay không. Nhưng điều này cho tôi cái gì đó để điều tra. – GWB

+0

Có, các phương pháp này có vẻ rất hạn chế trong sử dụng. Tôi chỉ có thể khiến họ làm việc trực tiếp trên bộ đối tượng. Ngay cả trên kết quả của một 'Where()', khi được cast một cách rõ ràng tới một ObjectQuery (các phương thức của trình xây dựng _Query không được hỗ trợ cho các truy vấn LINQ to Entities. Để biết thêm thông tin, hãy xem tài liệu Entity Framework._) –

Các vấn đề liên quan