I'm new to EF so apologies in advanced if something isn't done correctly. I'm struggling to get paging to work asynchronously with EF 6.
I've implemented the paging mechanism as per this article: How to Increase the Performance of Entity Framework with Paging, which I thought was clean and to the point (but not perfect either) but I can't get this to work asynchronously which is a problem.
As per article, I've create the interface:
public interface IPageList
{
int TotalCount { get; }
int PageCount { get; }
int Page { get; }
int PageSize { get; }
}
I created the class:
public class PageList<T> : List<T>, IPageList
{
public int TotalCount { get; private set; }
public int PageCount { get; private set; }
public int Page { get; private set; }
public int PageSize { get; private set; }
public PageList(IQueryable<T> source, int page, int pageSize)
{
TotalCount = source.Count();
PageCount = GetPageCount(pageSize, TotalCount);
Page = page < 1 ? 0 : page - 1;
PageSize = pageSize;
AddRange(source.Skip(Page * PageSize).Take(PageSize).ToList());
}
private int GetPageCount(int pageSize, int totalCount)
{
if (pageSize == 0)
return 0;
var remainder = totalCount % pageSize;
return (totalCount / pageSize) + (remainder == 0 ? 0 : 1);
}
}
and finally the extension:
public static class PageListExtensions
{
public static PageList<T> ToPageList<T>(this IQueryable<T> source, int pageNumber,
int pageSize)
{
return new PageList<T>(source, pageNumber, pageSize);
}
}
So in my data layer, I've got the following function:
public async Task<List<LogEntity>> GetLogsAsync(int pageNumber, int pageSize)
{
using (_dbContext = new DatabaseContext())
{
var results = _dbContext.Logs.Select(l => new
{
LogId = l.LogId,
Message = l.Message,
})
.OrderBy(o => o.DateTime)
.ToPageList(pageNumber, pageSize).ToList().Select(x => new LogEntity()
{
LogId = x.LogId,
Message = x.Message,
});
return await results.AsQueryable<LogEntity>().ToListAsync();
}
}
When I run the above, I get:
Additional information: The source IQueryable doesn't implement IDbAsyncEnumerable. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.
I've googled the error and while I've read numerous articles, I'm still struggling to get it to work.
Can anyone tell me exactly how to resolve this problem as I have no idea where to start at this stage.
Thanks
UPDATE-1
As Ivan highlighted in his comment, I don't think I need the 2 Select
, so here is the simplified version:
var results = _dbContext.Logs.OrderBy(o=>o.DateTime)
.ToPageList(pageNumber, pageSize).Select(l => new
{
LogId = l.LogId,
Message = l.Message,
});
Still doesn't sort my async problem. I'm currently looking at this article which will hopefully help:
How to return empty IQueryable in an async repository method
UPDATE-2
I think I figured it out but it still not as responsive as I'd like it to be, so I'm not 100% sure whether or not it is done correctly. I thought that when swapping to my logs tab in my WPF app, the swapping would have been instantaneous but it's not!
Anyway here's what I've changed:
public async Task<List<LogEntity>> GetLogsAsync(int pageNumber, int pageSize)
{
using (_dbContext = new DatabaseContext())
{
var results = _dbContext.Logs.OrderBy(o=>o.DateTime).ToPageList(pageNumber, pageSize).Select(l => new LogEntity
{
LogId = l.LogId,
Message = l.Message,
}).AsAsyncQueryable();
return await results.ToListAsync();
}
}
If anything, the code is definitely simpler than my original one.
Update-3:
When I call this:
return new PageList<LogEntity>(_dbContext.Logs, pageNumber, pageSize);
It returns the TotalCount = 100,000, PageCount = 200, Page = 0, PageSize 500, but then it throws an error when the AddRange is called i.e.
An exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll but was not handled in user code Additional information: The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.
So I fixed this by calling:
return new PageList<LogEntity>(_dbContext.Logs.OrderBy(o=>o.DateTime),
pageNumber, pageSize);
When I tried to call @krillgar's simplest suggestion i.e.
return _dbContext.Logs
.Select(l => new LogEntity // Cast here so your .ToPageList
{ // will start as the object type you want.
LogId = l.LogId,
Message = l.Message
})
.OrderBy(l => l.DateTime)
.ToPageList(pageNumber, pageSize);
I get the following error:
An exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll but was not handled in user code Additional information: The entity or complex type 'MyCompany.DataLayerSql.LogEntity' cannot be constructed in a LINQ to Entities query.
on the this.TotalCount = source.Count();
in the PageList class.
Any ideas?