Чи варто використовувати LINQ Skip()
та Take()
метод для підкачки, або реалізувати власний підказок із запитом SQL?
Що найефективніше? Чому я обрав би один над іншим?
Я використовую SQL Server 2008, ASP.NET MVC та LINQ.
Чи варто використовувати LINQ Skip()
та Take()
метод для підкачки, або реалізувати власний підказок із запитом SQL?
Що найефективніше? Чому я обрав би один над іншим?
Я використовую SQL Server 2008, ASP.NET MVC та LINQ.
Відповіді:
Намагаючись дати вам коротку відповідь на свої сумніви, якщо ви будете виконувати skip(n).take(m)
методи на linq (з SQL 2005/2008 як сервером бази даних), ваш запит буде використовувати Select ROW_NUMBER() Over ...
оператор, з якимось чином прямим пейджингом у двигуні SQL.
Надаючи вам приклад, у мене називається таблиця db, mtcity
і я написав наступний запит (також працюйте з linq для сутностей):
using (DataClasses1DataContext c = new DataClasses1DataContext())
{
var query = (from MtCity2 c1 in c.MtCity2s
select c1).Skip(3).Take(3);
//Doing something with the query.
}
Отриманий запит буде:
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]
Що є віконним доступом до даних (досить круто, btw cuz буде повертати дані з самого початку і матиме доступ до таблиці, доки умови будуть виконані). Це буде дуже схоже на:
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
За винятком того, що цей другий запит буде виконуватися швидше, ніж результат linq, оскільки він буде використовувати виключно індекс для створення вікна доступу до даних; це означає, що якщо вам потрібна деяка фільтрація, фільтрування має бути (або повинно бути) у списку Entity (де створений рядок), а також слід створити деякі індекси, щоб підтримувати хороші показники.
Тепер, що краще?
Якщо у вашій логіці є досить міцний робочий процес, реалізація правильного способу SQL буде складним. У цьому випадку рішенням буде LINQ.
Якщо ви можете опустити цю частину логіки безпосередньо до SQL (у збереженій процедурі), це буде ще краще, оскільки ви можете реалізувати другий запит, який я вам показав (використовуючи індекси), і дозволити SQL генерувати та зберігати План виконання програми запит (підвищення продуктивності).
Спробуйте використовувати
FROM [TableX]
ORDER BY [FieldX]
OFFSET 500 ROWS
FETCH NEXT 100 ROWS ONLY
щоб отримати рядки від 501 до 600 на SQL-сервері, не завантажуючи їх у пам'ять. Зверніть увагу , що цей синтаксис став доступний з SQL Server 2012 тільки
У той час як LINQ-SQL генерує OFFSET
застереження (можливо, емулюється за допомогою, ROW_NUMBER() OVER()
як згадували інші ), існує зовсім інший, набагато швидший спосіб виконання підкачки в SQL. Це часто називають "методом пошуку", як описано в цій публікації блогу тут .
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
Значення @previousScore
і @previousPlayerId
- відповідні значення останнього запису з попередньої сторінки. Це дозволяє отримати "наступну" сторінку. Якщо ORDER BY
напрямок є ASC
, просто використовуйте >
замість цього.
За допомогою описаного вище способу ви не можете відразу перейти на сторінку 4, попередньо не отримавши попередніх 40 записів. Але часто вам так і не хочеться стрибати так далеко. Натомість ви отримуєте набагато швидший запит, який може отримати дані за постійний час, залежно від вашої індексації. Крім того, ваші сторінки залишаються "стабільними", незалежно від того, чи змінюються основні дані (наприклад, на сторінці 1, поки ви перебуваєте на сторінці 4).
Це найкращий спосіб здійснити пейджинговий виклик, наприклад, при ледачому завантаженні більшої кількості даних у веб-додатках.
Зауважте, "метод пошуку" також називається пейджингом набору клавіш .
LinqToSql автоматично перетворить .Skip (N1) .Take (N2) в синтаксис TSQL для вас. Насправді кожен "запит", який ви робите в Linq, насправді просто створює для вас запит SQL у фоновому режимі. Щоб перевірити це, просто запустіть SQL Profiler під час роботи програми.
Методика пропуску / прийому дуже добре працювала для мене, а також інші, що я читав.
З цікавості, який тип запиту для самостійного підключення до сторінки, який ви вважаєте, є більш ефективним, ніж пропуск / прийняття Linq?
Ми використовуємо CTE, загорнутий у Dynamic SQL (оскільки наша програма вимагає динамічного сортування на стороні сервера даних) у межах збереженої процедури. Я можу навести основний приклад, якщо ви хочете.
У мене не було можливості подивитися T / SQL, який виробляє LINQ. Чи може хтось розмістити зразок?
Ми не використовуємо LINQ або прямий доступ до таблиць, оскільки нам потрібен додатковий рівень безпеки (за умови, що динамічний SQL дещо перетворює це).
Щось подібне повинно зробити трюк. Ви можете додавати параметризовані значення для параметрів тощо.
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'
sp_executesql
Вас є можливість передавати параметри в безпечному режимі, наприклад: EXECUTE sp_executesql 'WITH myCTE AS ... WHERE Col4=@p1) ...', '@p1 nvarchar(max)', @ValueForCol4
. Безпечний у цьому контексті означає, що він є надійним проти інжекцій SQL - ви можете передавати всі можливі значення всередині змінної @ValueForCol4
- навіть '--'
, і запит все одно буде працювати!
SELECT ROW_NUMBER() OVER (ORDER BY CASE WHEN @CampoId = 1 THEN Id WHEN @CampoId = 2 THEN field2 END)
ROW_NUMBER() OVER()
емуляція зміщення. Дивіться також: 4guysfromrolla.com/webtech/042606-1.shtml
У 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]
У t0 - всі записи, в t1 - лише ті, що відповідають цій сторінці
Підхід, який я даю, - це найшвидша сторінка, яку може досягти SQL-сервер. Я перевірив це на 5 мільйонах записів. Цей підхід набагато кращий, ніж "OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY", наданий SQL Server.
-- The below given code computes the page numbers and the max row of previous page
-- Replace <<>> with the correct table data.
-- Eg. <<IdentityColumn of Table>> can be EmployeeId and <<Table>> will be dbo.Employees
DECLARE @PageNumber int=1; --1st/2nd/nth page. In stored proc take this as input param.
DECLARE @NoOfRecordsPerPage int=1000;
DECLARE @PageDetails TABLE
(
<<IdentityColumn of Table>> int,
rownum int,
[PageNumber] int
)
INSERT INTO @PageDetails values(0, 0, 0)
;WITH CTE AS
(
SELECT <<IdentityColumn of Table>>, ROW_NUMBER() OVER(ORDER BY <<IdentityColumn of Table>>) rownum FROM <<Table>>
)
Insert into @PageDetails
SELECT <<IdentityColumn of Table>>, CTE.rownum, ROW_NUMBER() OVER (ORDER BY rownum) as [PageNumber] FROM CTE WHERE CTE.rownum%@NoOfRecordsPerPage=0
--SELECT * FROM @PageDetails
-- Actual pagination
SELECT TOP (@NoOfRecordsPerPage)
FROM <<Table>> AS <<Table>>
WHERE <<IdentityColumn of Table>> > (SELECT <<IdentityColumn of Table>> FROM
@PageDetails WHERE PageNumber=@PageNumber)
ORDER BY <<Identity Column of Table>>
Ви можете додатково покращити продуктивність, це досягати
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
якщо ви будете використовувати цей спосіб таким чином, це дасть кращий результат:
From dbo.MtCity t0
Inner Join CityEntities c on c.CodCity = t0.CodCity
Причина: тому що ви використовуєте клас у таблиці CityEntities, який усуне багато записів перед приєднанням до MtCity, тому на 100% впевнений, що це збільшить продуктивність у багато разів ...
У будь-якому випадку відповідь rodrigoelp справді корисна.
Дякую
@p0
і конкретніше @p1
беруться
Ви можете реалізувати пейджінг цим простим способом, передавши 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
У 2008 році ми не можемо використовувати Skip (). Take ()
Шлях:
var MinPageRank = (PageNumber - 1) * NumInPage + 1
var MaxPageRank = PageNumber * NumInPage
var visit = Visita.FromSql($"SELECT * FROM (SELECT [RANK] = ROW_NUMBER() OVER (ORDER BY Hora DESC),* FROM Visita WHERE ) A WHERE A.[RANK] BETWEEN {MinPageRank} AND {MaxPageRank}").ToList();