views:

95

answers:

3

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
+1  A: 

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 ScottGu blog post.

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

Jan Jongboom
Doing ToList() at the end. Example: db.Stores.OrderBy(x => x.Name).Skip(5).Take(5).ToList()
Sam
Yes, but earlier on. Is some other method doing a ToList() on your original set.
Jan Jongboom
Posted code above...
Sam
The code above won't return all your data. You either have to better look into the profiler, or you are doing `ToList` on some other place in code to this data. Try running the profiler when only doing this piece of code.
Jan Jongboom
Sam
A: 

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.

Bryce Fischer
results is an ObjectSet<Store> type. The results.OrderByDescending(sort) returns IOrderedEnumerable<Store> so it can't be assigned to results. You gave me a couple ideas on things to try though. Will post back once I've tested.
Sam
that's what they all return. Until you specify a .Select() or the .ToList().
Bryce Fischer
Right, but the compiler prevents "if (desc) results = results.OrderByDescending(sort)" because the types are different. I also found out that I get an error when I use .Skip without calling .OrderBy first (happens when !desc). Posted edits to my original post with additional findings.
Sam
A: 

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
Sam