0

I need to merge these two methods in only one generalizing them. I know IQueryable implements IEnumerable, IOrderedQueryable implements IOrderedEnumerable and the first method seems to be useless if the second is present. But for some Entity Framework reasons the second one breaks the server-side query translating (Linq-to-SQL)

    private static IQueryable<T> Pag<T>(IOrderedQueryable<T> data, int totalRows, int reqLength, int pageNum)
    {
        if (reqLength == 0)
            return data;
        else
        {
            int skipRows = reqLength * (pageNum - 1);
            if (skipRows >= totalRows)
                skipRows = 0;
            int diff = totalRows - skipRows;
            return data.Skip(skipRows).Take(diff > reqLength ? reqLength : diff);
        }
    }

    private static IEnumerable<T> Pag<T>(IOrderedEnumerable<T> data, int totalRows, int reqLength, int pageNum)
    {
        if (reqLength == 0)
            return data;
        else
        {
            int skipRows = reqLength * (pageNum - 1);
            if (skipRows >= totalRows)
                skipRows = 0;
            int diff = totalRows - skipRows;
            return data.Skip(skipRows).Take(diff > reqLength ? reqLength : diff);
        }
    }

These methods break the DRY rule and it's awfully annoying.

Max
  • 13
  • 2
  • 1
    The obfuscation of your code succeeded. Learn to use self-descriptive variable names. That being said, show how you call this code. – CodeCaster Jan 10 '17 at 15:55
  • You need to show a complete, minifiable and verifiable example to show EF failing to translate in Linq to SQL. – silentsod Jan 10 '17 at 15:58
  • This code simply paginates the results of a DB query. If linq works over IQueryable collection, linq-to-sql makes its job to retrieve just the needed rows from the DB. Commenting the IQueryable method, the code works anyway using the IEnumerable method, but the query retrieves ALL data from the DB processing itself instead of leaving the job to the DB with detrimental performance effects. – Max Jan 11 '17 at 10:01
  • Why do you need to specify totalRows?. Take(diff > reqLength ? reqLength : diff) the conditional count inside the Take is not required (definitely for IEnumerable) and I'm sure it's the case for most of the other DataProviders. It could simply be Take(totalRows).Skip(...).Take(reqLength) as that param is permissive if you specify a value greater than what is available. – Nasmi Sabeer Jan 11 '17 at 11:32
  • I used Count() before in the code. It's preferable don't call it more than necessary. – Max Jan 11 '17 at 13:28

1 Answers1

0

Even if the code looks the same, they don't work on the same type and generics also can't be used, cause the where clause can only AND combine multiple given values.

So all you can do, is factor out the calculation logic, but if this is really better readable and maintainable really depends on your attitude.

private static bool TryCalculateRange(int totalCount, int pageLength, int page, out int skip, out int take)
{
    skip = 0;
    take = 0;

    if(pageLength <= 0)
        return false;

    skip = pageLength * (page - 1);

    if(skip >= totalCount)
        skip = 0;

    take = totalCount - skip;

    if(take > pageLength)
        take = pageLength;

    return true;
}

public static IQueryable<T> Pag<T>(IOrderedQueryable<T> data, int totalRows, int reqLength, int pageNum)
{
    int skip;
    int take;

    return TryCalculateRange(totalRows, reqLength, pageNum, out skip, out take))
           ? data.Skip(skip).Take(take)
           : data;
}

public static IEnumerable<T> Pag<T>(IOrderedEnumerable<T> data, int totalRows, int reqLength, int pageNum)
{
    int skip;
    int take;

    return TryCalculateRange(totalRows, reqLength, pageNum, out skip, out take))
           ? data.Skip(skip).Take(take)
           : data;
    }
}
Oliver
  • 43,366
  • 8
  • 94
  • 151