ฉันควรใช้ 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 เพราะจะส่งคืนข้อมูลตั้งแต่เริ่มต้นและจะเข้าถึงตารางตราบเท่าที่ตรงตามเงื่อนไข) สิ่งนี้จะคล้ายกับ:
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 เนื่องจากจะใช้เฉพาะดัชนีเพื่อสร้างหน้าต่างการเข้าถึงข้อมูล ซึ่งหมายความว่าหากคุณต้องการการกรองการกรองควรเป็น (หรือต้อง) ในรายการเอนทิตี (ที่สร้างแถว) และควรสร้างดัชนีบางรายการด้วยเพื่อให้ได้ประสิทธิภาพที่ดี
ตอนนี้อะไรดีขึ้น?
หากคุณมีขั้นตอนการทำงานที่ค่อนข้างมั่นคงในตรรกะของคุณการใช้วิธี SQL ที่เหมาะสมจะมีความซับซ้อน ในกรณีนี้ LINQ จะเป็นทางออก
หากคุณสามารถลดส่วนนั้นของตรรกะลงใน SQL ได้โดยตรง (ในขั้นตอนที่เก็บไว้) จะดียิ่งขึ้นเพราะคุณสามารถใช้แบบสอบถามที่สองที่ฉันแสดงให้คุณเห็น (โดยใช้ดัชนี) และอนุญาตให้ SQL สร้างและจัดเก็บแผนการดำเนินการของ แบบสอบถาม (ปรับปรุงประสิทธิภาพ)
ลองใช้
FROM [TableX]
ORDER BY [FieldX]
OFFSET 500 ROWS
FETCH NEXT 100 ROWS ONLY
เพื่อรับแถวจาก 501 ถึง 600 ในเซิร์ฟเวอร์ SQL โดยไม่ต้องโหลดในหน่วยความจำ โปรดทราบว่าไวยากรณ์นี้สามารถใช้ได้กับSQL Server 2012เท่านั้น
ในขณะที่ LINQ-to-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) โดยอัตโนมัติใช้ (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 ในลักษณะนี้จะให้ผลลัพธ์ที่ดีกว่า:
From dbo.MtCity t0
Inner Join CityEntities c on c.CodCity = t0.CodCity
เหตุผล: เนื่องจากคุณใช้คลาส where บนตาราง 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();