Entity Framework/Linq to SQL: Skip & Take

C#.NetLinq to-SqlSql Server-2008Entity Framework-4

C# Problem Overview


Just curious as to how Skip & Take are supposed to work. I'm getting the results I want to see on the client side, but when I hook up the AnjLab SQL Profiler and look at the SQL that is being executed it looks as though it is querying for and returning the entire set of rows to the client.

Is it really returning all the rows then sorting and narrowing down stuff with LINQ on the client side?

I've tried doing it with both Entity Framework and Linq to SQL; both appear to have the same behavior.

Not sure it makes any difference, but I'm using C# in VWD 2010.

Any insight?

public IEnumerable<Store> ListStores(Func<Store, string> sort, bool desc, int page, int pageSize, out int totalRecords)
{
	var context = new TectonicEntities();
	totalRecords = context.Stores.Count();
	int skipRows = (page - 1) * pageSize;
	if (desc)
		return context.Stores.OrderByDescending(sort).Skip(skipRows).Take(pageSize).ToList();
	return context.Stores.OrderBy(sort).Skip(skipRows).Take(pageSize).ToList();
}

Resulting SQL (Note: I'm excluding the Count query):

SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[Name] AS [Name], 
[Extent1].[LegalName] AS [LegalName], 
[Extent1].[YearEstablished] AS [YearEstablished], 
[Extent1].[DiskPath] AS [DiskPath], 
[Extent1].[URL] AS [URL], 
[Extent1].[SecureURL] AS [SecureURL], 
[Extent1].[UseSSL] AS [UseSSL]
FROM [dbo].[tec_Stores] AS [Extent1]

After some further research, I found that the following works the way I would expect it to:

public IEnumerable<Store> ListStores(Func<Store, string> sort, bool desc, int page, int pageSize, out int totalRecords)
{
	var context = new TectonicEntities();
	totalRecords = context.Stores.Count();
	int skipRows = (page - 1) * pageSize;			
	var qry = from s in context.Stores orderby s.Name ascending select s;
	return qry.Skip(skipRows).Take(pageSize);			
}

Resulting SQL:

SELECT TOP (3) 
[Extent1].[ID] AS [ID], 
[Extent1].[Name] AS [Name], 
[Extent1].[LegalName] AS [LegalName], 
[Extent1].[YearEstablished] AS [YearEstablished], 
[Extent1].[DiskPath] AS [DiskPath], 
[Extent1].[URL] AS [URL], 
[Extent1].[SecureURL] AS [SecureURL], 
[Extent1].[UseSSL] AS [UseSSL]
FROM ( SELECT [Extent1].[ID] AS [ID], [Extent1].[Name] AS [Name], [Extent1].[LegalName] AS [LegalName], [Extent1].[YearEstablished] AS [YearEstablished], [Extent1].[DiskPath] AS [DiskPath], [Extent1].[URL] AS [URL], [Extent1].[SecureURL] AS [SecureURL], [Extent1].[UseSSL] AS [UseSSL], row_number() OVER (ORDER BY [Extent1].[Name] ASC) AS [row_number]
	FROM [dbo].[tec_Stores] AS [Extent1]
)  AS [Extent1]
WHERE [Extent1].[row_number] > 3
ORDER BY [Extent1].[Name] ASC

I really like the way the first option works; Passing in a lambda expression for sort. Is there any way to accomplish the same thing in the LINQ to SQL orderby syntax? I tried using qry.OrderBy(sort).Skip(skipRows).Take(pageSize), but that ended up giving me the same results as my first block of code. Leads me to believe my issues are somehow tied to OrderBy.

==================================== PROBLEM SOLVED

Had to wrap the incoming lambda function in Expression:

Expression<Func<Store,string>> sort

C# Solutions


Solution 1 - C#

The following works and accomplishes the simplicity I was looking for:

public IEnumerable<Store> ListStores(Expression<Func<Store, string>> sort, bool desc, int page, int pageSize, out int totalRecords)
{
	List<Store> stores = new List<Store>();
	using (var context = new TectonicEntities())
	{
		totalRecords = context.Stores.Count();
		int skipRows = (page - 1) * pageSize;
		if (desc)
			stores = context.Stores.OrderByDescending(sort).Skip(skipRows).Take(pageSize).ToList();
		else
			stores = context.Stores.OrderBy(sort).Skip(skipRows).Take(pageSize).ToList();
	}
	return stores;
}

The main thing that fixed it for me was changing the Func sort parameter to:

Expression<Func<Store, string>> sort

Solution 2 - C#

As long as you don't do it like queryable.ToList().Skip(5).Take(10), it won't return the whole recordset.

Take

Doing only Take(10).ToList(), does a SELECT TOP 10 * FROM.

Skip

Skip works a bit different because there is no 'LIMIT' function in TSQL. However it creates an SQL query that is based on the work described in this http://weblogs.asp.net/scottgu/archive/2006/01/01/434314.aspx">ScottGu blog post.

If you see the whole recordset returned, it probably is because you are doing a ToList() somewhere too early.

Solution 3 - C#

Entity Framework 6 solution here...

http://anthonychu.ca/post/entity-framework-parameterize-skip-take-queries-sql/

e.g.

using System.Data.Entity;
....

int skip = 5;
int take = 10;

myQuery.Skip(() => skip).Take(() => take);

Solution 4 - C#

I created simple extension:

public static IEnumerable<T> SelectPage<T, T2>(this IEnumerable<T> list, Func<T, T2> sortFunc, bool isDescending, int index, int length)
{
    List<T> result = null;
    if (isDescending)
        result = list.OrderByDescending(sortFunc).Skip(index).Take(length).ToList();
    else
        result = list.OrderBy(sortFunc).Skip(index).Take(length).ToList();
    return result;
}

Simple use:

using (var context = new TransportContext())
{
    var drivers = (from x in context.Drivers where x.TransportId == trasnportId select x).SelectPage(x => x.Id, false, index, length).ToList();
}

Solution 5 - C#

If you are using SQL Server as DB

Then you can convert

context.Users.OrderBy(u => u.Id)
.Skip(() => 10)
.Take(() => 5)
.ToList

=>

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[UserName] AS [UserName]
FROM [dbo].[AspNetUsers] AS [Extent1]
ORDER BY [Extent1].[Id] ASC
OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY

refrence: https://anthonychu.ca/post/entity-framework-parameterize-skip-take-queries-sql/

Solution 6 - C#

Try this:

public IEnumerable<Store> ListStores(Func<Store, string> sort, bool desc, int page, int pageSize, out int totalRecords)
{
    var context = new TectonicEntities();
    var results = context.Stores;

    totalRecords = results.Count();
    int skipRows = (page - 1) * pageSize;

    if (desc)
        results = results.OrderByDescending(sort);

    return results.Skip(skipRows).Take(pageSize).ToList();
}

in truth, that last .ToList() isn't really necessary as you are returning IEnumerable...

There will be 2 database calls, one for the count and one when the ToList() is executed.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSamView Question on Stackoverflow
Solution 1 - C#SamView Answer on Stackoverflow
Solution 2 - C#Jan JongboomView Answer on Stackoverflow
Solution 3 - C#MickView Answer on Stackoverflow
Solution 4 - C#Ali YousefiView Answer on Stackoverflow
Solution 5 - C#Amit Singh RawatView Answer on Stackoverflow
Solution 6 - C#Bryce FischerView Answer on Stackoverflow