2009-02-14 23 views
111

Tôi có nên sử dụng phương thức Skip()Take() của LINQ để phân trang hoặc triển khai phân trang của riêng tôi với truy vấn SQL không?cách hiệu quả để triển khai phân trang

Cách nào hiệu quả nhất? Tại sao tôi lại chọn cái khác?

Tôi đang sử dụng SQL Server 2008, ASP.NET MVC và LINQ.

+0

Tôi nghĩ điều đó phụ thuộc. Bạn đang làm việc với ứng dụng nào? loại tải nó sẽ có? – BuddyJoe

+0

Hãy xem câu trả lời này: http: // stackoverflow.com/a/10639172/416996 –

+0

Hãy xem điều này cũng http://www.aspsnippets.com/Articles/Custom-Paging-in-ASP.Net-GridView-using-SQL-Server-Stored-Procedure.aspx –

Trả lời

169

Đang cố gắng để cung cấp cho bạn một câu trả lời ngắn gọn để nghi ngờ của bạn, nếu bạn thực hiện các phương pháp trên skip(n).take(m) LINQ (với SQL 2005/2008 như máy chủ cơ sở dữ liệu) truy vấn của bạn sẽ đang sử dụng câu lệnh Select ROW_NUMBER() Over ..., với phân trang trực tiếp bằng cách nào đó trong công cụ SQL.

Đem lại cho bạn một ví dụ, tôi có một bảng db gọi mtcity và tôi đã viết các truy vấn sau đây (công việc cũng như với LINQ to Entities):

using (DataClasses1DataContext c = new DataClasses1DataContext()) 
{ 
    var query = (from MtCity2 c1 in c.MtCity2s 
       select c1).Skip(3).Take(3); 
    //Doing something with the query. 
} 

Các kết quả truy vấn sẽ là:

SELECT [t1].[CodCity], 
    [t1].[CodCountry], 
    [t1].[CodRegion], 
    [t1].[Name], 
    [t1].[Code] 
FROM (
    SELECT ROW_NUMBER() OVER (
     ORDER BY [t0].[CodCity], 
     [t0].[CodCountry], 
     [t0].[CodRegion], 
     [t0].[Name], 
     [t0].[Code]) AS [ROW_NUMBER], 
     [t0].[CodCity], 
     [t0].[CodCountry], 
     [t0].[CodRegion], 
     [t0].[Name], 
     [t0].[Code] 
    FROM [dbo].[MtCity] AS [t0] 
    ) AS [t1] 
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1 
ORDER BY [t1].[ROW_NUMBER] 

Đó là quyền truy cập dữ liệu cửa sổ (khá thú vị, btw cuz sẽ trả về dữ liệu kể từ khi bắt đầu và sẽ truy cập vào bảng miễn là đáp ứng các điều kiện). Điều này sẽ rất giống với:

With CityEntities As 
(
    Select ROW_NUMBER() Over (Order By CodCity) As Row, 
     CodCity //here is only accessed by the Index as CodCity is the primary 
    From dbo.mtcity 
) 
Select [t0].[CodCity], 
     [t0].[CodCountry], 
     [t0].[CodRegion], 
     [t0].[Name], 
     [t0].[Code] 
From CityEntities c 
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity 
Where c.Row Between @p0 + 1 AND @p0 + @p1 
Order By c.Row Asc 

Với ngoại lệ, truy vấn thứ hai này sẽ được thực hiện độc quyền để tạo cửa sổ truy cập dữ liệu; điều này có nghĩa, nếu bạn cần lọc, bộ lọc nên (hoặc phải) trong danh sách Thực thể (nơi hàng được tạo) và một số chỉ mục cũng sẽ được tạo để duy trì hiệu suất tốt.

Bây giờ, còn gì tốt hơn?

Nếu bạn có khá nhiều công việc vững chắc trong logic của mình, việc triển khai đúng cách SQL sẽ phức tạp. Trong trường hợp đó LINQ sẽ là giải pháp.

Nếu bạn có thể giảm phần logic trực tiếp xuống SQL (trong thủ tục đã lưu), nó sẽ còn tốt hơn vì bạn có thể thực hiện truy vấn thứ hai mà tôi đã chỉ cho bạn (sử dụng chỉ mục) và cho phép SQL tạo và lưu trữ Kế hoạch thực hiện truy vấn (cải thiện hiệu suất).

+2

Câu trả lời hay - biểu thức bảng chung là một cách hay để thực hiện phân trang. –

+0

Bạn có thể kiểm tra câu hỏi của tôi không (http://stackoverflow.com/questions/11100929/asp-net-mvc-webgrid-efficent-paging/11104822#comment14552347_11104822)? Tôi đã tạo một SP mà tôi đã thêm vào EDMX của mình và sử dụng nó trong một truy vấn LINQ-to-entity. – Misi

+2

+1, câu trả lời hay, tôi đánh giá cao bạn giải thích lợi ích hiệu suất của ví dụ thứ hai – Cohen

5

LinqToSql sẽ tự động chuyển đổi .Skip (N1) .Take (N2) thành cú pháp TSQL cho bạn. Trong thực tế, mỗi "truy vấn" bạn làm trong Linq, thực sự chỉ là tạo một truy vấn SQL cho bạn trong nền. Để kiểm tra điều này, chỉ cần chạy SQL Profiler trong khi ứng dụng của bạn đang chạy.

Phương pháp bỏ qua/lấy đã hoạt động rất tốt đối với tôi và những phương pháp khác từ những gì tôi đọc.

Ngoài sự tò mò, bạn có loại truy vấn tự phân trang nào, bạn cho rằng hiệu quả hơn so với bỏ qua/lấy của LINQ?

4

Chúng tôi sử dụng CTE được bao bọc trong SQL động (vì ứng dụng của chúng tôi yêu cầu phân loại động bên máy chủ dữ liệu) trong một thủ tục được lưu trữ. Tôi có thể cung cấp một ví dụ cơ bản nếu bạn muốn.

Tôi chưa có cơ hội để xem T/SQL mà LINQ tạo ra. Ai đó có thể gửi mẫu?

Chúng tôi không sử dụng LINQ hoặc truy cập thẳng vào các bảng vì chúng tôi yêu cầu lớp bảo mật bổ sung (cấp cho SQL động ngắt phần này).

Một cái gì đó như thế này nên làm các trick. Bạn có thể thêm vào các giá trị tham số cho các thông số vv

exec sp_executesql 'WITH MyCTE AS (
    SELECT TOP (10) ROW_NUMBER() OVER ' + @SortingColumn + ' as RowID, Col1, Col2 
    FROM MyTable 
    WHERE Col4 = ''Something'' 
) 
SELECT * 
FROM MyCTE 
WHERE RowID BETWEEN 10 and 20' 
+2

@ mrdenny - Một ** gợi ý cho ví dụ ** bạn đã cung cấp: Với 'sp_executesql' bạn có khả năng truyền tham số một cách an toàn, ví dụ:' EXECUTE sp_executesql 'VỚI myCTE AS ... WHERE Col4 = @ p1) ... ',' @ p1 nvarchar (tối đa) ', @ ValueForCol4'. Bảo mật trong ngữ cảnh này có nghĩa là nó mạnh mẽ chống lại SQL injection - bạn có thể truyền mọi giá trị có thể bên trong biến '@ ValueForCol4' - thậm chí' '-' ', và truy vấn sẽ vẫn hoạt động! – Matt

+1

@mrdenny Hi, thay vì concatenating truy vấn chúng tôi sử dụng một cái gì đó như thế này: 'SELECT ROW_NUMBER() OVER (ORDER BY TRƯỜNG HỢP KHI @CampoId = 1 THEN Id KHI @CampoId = 2 THEN field2 END)' – Ezequiel

+0

Đó có thể sản xuất một số kế hoạch SQL Execution khủng khiếp. – mrdenny

0

bạn có thể tiếp tục cải thiện hiệu suất, Chech này

From CityEntities c 
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity 
Where c.Row Between @p0 + 1 AND @p0 + @p1 
Order By c.Row Asc 

nếu bạn sẽ sử dụng các từ theo cách này nó sẽ cho kết quả tốt hơn:

From dbo.MtCity t0 
    Inner Join CityEntities c on c.CodCity = t0.CodCity 

lý do: vì bạn đang sử dụng ở đâu lớp học trên bảng CityEntities mà sẽ loại bỏ nhiều kỷ lục trước khi gia nhập MtCity, do đó, 100% chắc chắn nó sẽ tăng hiệu suất nhiều lần ...

Anyway answer by rodrigoelp i s thực sự hữu ích.

Cảm ơn

+0

Tôi nghi ngờ sẽ có bất kỳ tác động hiệu suất nào bằng cách sử dụng lời khuyên này. Không thể tìm thấy tham chiếu cho điều này nhưng thứ tự kết nối bên trong trong truy vấn có thể khác với thứ tự tham gia thực tế. Cái sau được quyết định bởi trình tối ưu hóa truy vấn bằng cách sử dụng các số liệu thống kê và ước tính chi phí hoạt động của bảng. –

+0

@ImreP: Điều này thực sự có thể tương ứng với [phương pháp tìm kiếm, mà tôi đã mô tả] (http://stackoverflow.com/a/19609720/521799). Mặc dù, tôi không chắc chắn nơi '@ p0' và cụ thể hơn' @ p1' đến từ –

47

Hãy thử sử dụng

FROM [TableX] 
ORDER BY [FieldX] 
OFFSET 500 ROWS 
FETCH NEXT 100 ROWS ONLY 

để có được các hàng 501-600 trong máy chủ SQL, mà không cần tải chúng trong bộ nhớ. Lưu ý rằng cú pháp này đã trở thành có sẵn với SQL Server 2012 chỉ

+0

Tôi nghĩ rằng điều này là không chính xác. SQL được hiển thị hiển thị các hàng từ 502-601 (trừ khi bạn không lập chỉ mục?) – Smudge202

10

Trong khi LINQ-to-SQL sẽ tạo ra một khoản OFFSET (có thể mô phỏng sử dụng ROW_NUMBER() OVER()as others have mentioned), có một cách hoàn toàn khác nhau nhanh hơn nhiều để thực hiện phân trang trong SQL. Điều này thường được gọi là "phương pháp tìm kiếm" như được mô tả trong this blog post here.

SELECT TOP 10 first_name, last_name, score 
FROM players 
WHERE (score < @previousScore) 
    OR (score = @previousScore AND player_id < @previousPlayerId) 
ORDER BY score DESC, player_id DESC 

Các @previousScore@previousPlayerId giá trị là những giá trị tương ứng của hồ sơ cuối cùng từ trang trước. Điều này cho phép bạn tìm nạp trang "tiếp theo". Nếu hướng ORDER BYASC, chỉ cần sử dụng > thay thế.

Với phương pháp trên, bạn không thể chuyển ngay sang trang 4 mà không tìm nạp trước 40 bản ghi trước đó. Nhưng thường thì bạn không muốn nhảy xa đến thế. Thay vào đó, bạn nhận được truy vấn nhanh hơn nhiều có thể tìm nạp dữ liệu trong thời gian không đổi, tùy thuộc vào việc lập chỉ mục của bạn. Ngoài ra, các trang của bạn vẫn "ổn định", bất kể dữ liệu cơ bản có thay đổi hay không (ví dụ: trên trang 1, trong khi bạn đang ở trang 4).

Đây là cách tốt nhất để thực hiện phân trang khi tải xuống dữ liệu nhiều hơn trong ứng dụng web chẳng hạn.

Lưu ý, "phương pháp tìm kiếm" cũng được gọi là keyset paging.

1

Trong SQL Server 2008:

DECLARE @PAGE INTEGER = 2 
DECLARE @TAKE INTEGER = 50 

SELECT [t1].* 
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[COLUMNORDER] DESC) AS [ROW_NUMBER], [t0].* 
    FROM [dbo].[TABLA] AS [t0] 
    WHERE ([t0].[COLUMNS_CONDITIONS] = 1) 
    ) AS [t1] 
WHERE [t1].[ROW_NUMBER] BETWEEN ((@PAGE*@TAKE) - (@TAKE-1)) AND (@PAGE*@TAKE) 
ORDER BY [t1].[ROW_NUMBER] 

Trong t0 là tất cả hồ sơ Trong t1 là chỉ những tương ứng với trang đó

0

Bạn có thể thực hiện phân trang theo cách đơn giản này bằng cách thông qua PageIndex

Declare @PageIndex INT = 1 
Declare @PageSize INT = 20 

Select ROW_NUMBER() OVER (ORDER BY Products.Name ASC) AS RowNumber, 
    Products.ID, 
    Products.Name 
into #Result 
From Products 

SELECT @RecordCount = COUNT(*) FROM #Results 

SELECT * 
FROM #Results 
WHERE RowNumber 
BETWEEN 
    (@PageIndex -1) * @PageSize + 1 
    AND 
    (((@PageIndex -1) * @PageSize + 1) + @PageSize) - 1 
Các vấn đề liên quan