Getting COUNT and SKIP TAKE in one operation with

2019-02-18 07:55发布

问题:

I have a data call in a Linq to Entities powered data access layer that is designed to make paged calls.

In doing so, I need to select a subset of the data, say 50 rows, but also get the count of all matches to know how many total matches exist to be paged through.

Currently, I'm doing the following:

var queryResult = DatabaseContext.Table
    .Where(x => !x.IsDeleted)
    .Where(p => (
            p.PropertyOne.ToLower().Contains(query) ||
            p.PropertyTwo.ToLower().Contains(query) 
            ));

int count = queryResult.Count();

var returnData = queryResult
    .OrderBy(i => i.ID)
    .Skip(start).Take((length))
    .Select(y => new ObjectDTO
    {
        PropertyOne = y.PropertyOne,
        PropertyTwo = y.PropertyTwo
    }
    .AsEnumerable();

This results in two costly database operations. The COUNT operation for some reason actually takes longer than the SELECT operation.

Is there a way to get count and a subset in the same operation?

The logical flow to me says we do the following:

  • Look at Table
  • Find Items in Table that match a condition
  • Get a Count of all matches
  • Return a numbered subset of the matches

This seems possible in one operation, but I cannot figure out how.

Attempt One, Slower

Tried D Stanley's suggestion of casting the full result set to a List and doing count and memory in paging, but it is roughly 2x slower (6.9s avg vs 3.9s avg)

It's worth mentioning the data set is roughly 25,000 records, with over a dozen related tables that are searched in a JOIN.

回答1:

It may be possible, but it probably won't be much faster because of the criteria you're using. Since you're searching for text within a column value, you cannot use an index and thus must do a table scan. You could do a single query to get all records and do the Count and Skip/Take in linq-to-objects:

var queryResult = DatabaseContext.Table
    .Where(x => !x.IsDeleted)
    .OrderBy(i => i.ID)
    .Where(p => (
            p.PropertyOne.ToLower().Contains(query) ||
            p.PropertyTwo.ToLower().Contains(query) 
            ))
    .ToList();

int count = queryResult.Count();  // now this will be a linq-to-objects query

var returnData = queryResult
    .Skip(start).Take((length))
    .AsEnumerable();

but you'd have to try it to see if it would be any faster.