简体   繁体   中英

Entity Framework async record count with records

I'm trying to fetch records from database but in the same call to DB get record count. My methods are async so I'm not sure if this is the right way to do it:

    public async Task<IEnumerable<ProductItemViewModel>> GetAsync(int pageIndex, int pageSize)
    {
        var products = this.Repository.GetAll<Product>().OrderBy(p => p.Price).AsQueryable();

        var count = await products.CountAsync();
        if (count == 0)
            return new List<ProductItemViewModel>();

        products = products.ToPaginatedList(pageIndex, pageSize);
        var result = await products.ToListAsync();

        var viewModels = new List<ProductItemViewModel>();
        Mapper.Map(result, viewModels);

        return viewModels.AsEnumerable();
    }

I think its not so good to await CountAsync() but I'm not sure how to implement it.

Regards.

EDIT:

I apologize for not returning count anywhere, as it looks now that I only count because of that == 0 check, but I will eventually return count with records because I need it for my angular paging directive. Haven't figured it out how I will return (new class vs tuple) so I missed it out and focused on entity framework Count() and entity fetch in single DB call.

I think your code can be optimized to this:

using AutoMapper.QueryableExtensions;
//...
public async Task<IEnumerable<ProductItemViewModel>> GetAsync(int pageIndex, int pageSize)
{
    var products = this.Repository.GetAll<Product>().OrderBy(p => p.Price);// I think you don't need to call AsQueryable(), you already are working with IQueryable
    var result=await products.Skip(pageIndex*pageSize).Take(pageSize).ProjectTo<ProductItemViewModel>().ToListAsync();
    return result;
}

At the end it can be done in just one line:

public async Task<IEnumerable<ProductItemViewModel>> GetAsync(int pageIndex, int pageSize)
{

    return await this.Repository.GetAll<Product>()
                                .OrderBy(p => p.Price)
                                .Skip(pageIndex*pageSize)
                                .Take(pageSize)
                                .ProjectTo<ProductItemViewModel>()
                                .ToListAsync();
}

The .ProjectTo<ProductItemViewModel>() will tell AutoMapper's mapping engine to emit a select clause to the IQueryable that will inform Entity Framework that it only needs to query the properties that you mapped, same as if you manually projected your IQueryable to an ProductItemViewModel with a Select clause.

Oops, I totally missed your return type. I thought you were using the count to display a TotalResultCount along with your paginated collection. Since you aren't, cut that out completely.

Just drop the CountAsync() call, and check the results.Count property for zero. That won't be the total number of results you have, but it'll tell you whether it's empty or not.

public async Task<IEnumerable<ProductItemViewModel>> GetAsync(int pageIndex, int pageSize)
{
    var result = await this.Repository.GetAll<Product>()
        .OrderBy(p => p.Price)
        .ToPaginatedList(pageIndex, pageSize)
        .ToListAsync();

    if (result.Count == 0)
    {
        // This "if" really isn't necessary, as your mapper should map an
        // empty collection to an empty collection. But it is a minor
        // efficiency improvement, and it speaks to your original code.

        return Enumerable.Empty<ProductItemViewModel>();
    }

    var viewModels = new List<ProductItemViewModel>();
    Mapper.Map(result, viewModels);

    return viewModels; // AsEnumerable() has no effect here, as the cast will happen by
                       // default anyway
}

This avoids the extra call to the server, at the cost of that variable's meaning. Read below this line if your pagination supports telling the user agent how many pages there are, for example, but other than that, this code should handle what you're doing here.


This answer holds an incorrect assumption that you needed to know the total result count; I originally read the code incorrectly.

It sounds like what you've got is going to be your best bet.

I don't know of any way in Entity Framework to use T-SQL's COUNT(*) OVER () clause, which is really what it sounds like you're looking for.

The only trouble with your approach here is that you'll be running two queries against your server:

  • How many records are there?
  • Give me the records x..y

That's less efficient than if you could grab them together, but it probably doesn't make a huge difference in a majority of applications. If you're worried about that performance cost, I'd reevaluate indexes you may have, or even think about alternatives to Entity Framework for this.

The fact that this is all async doesn't make any difference. Calling await against CountAsync() is exactly how it's meant to be used.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM