我是EF的新手,如果某些事情没有正确完成,请提前告知。我正在努力让分页与EF 6异步工作。
我按照这篇文章实现了分页机制:How to Increase the Performance of Entity Framework with Paging,我认为它很干净而且非常重要(但也不完美)但是我不能让它以异步方式工作这是一个问题。
根据文章,我创建了界面:
public interface IPageList
{
int TotalCount { get; }
int PageCount { get; }
int Page { get; }
int PageSize { get; }
}
我创建了这个类:
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);
}
}
最后是扩展名:
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);
}
}
所以在我的数据层中,我有以下功能:
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();
}
}
当我运行上述内容时,我得到:
附加信息:源IQueryable没有实现 IDbAsyncEnumerable。只有消息来源 实现IDbAsyncEnumerable可用于Entity Framework 异步操作。有关详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=287068
我已经搜索了这个错误,虽然我读过很多文章,但我仍然在努力让它发挥作用。
任何人都可以告诉我具体如何解决这个问题,因为我不知道从哪个阶段开始。
由于
UPDATE-1
正如Ivan在评论中强调的那样,我认为我不需要2 Select
,所以这里是简化版本:
var results = _dbContext.Logs.OrderBy(o=>o.DateTime)
.ToPageList(pageNumber, pageSize).Select(l => new
{
LogId = l.LogId,
Message = l.Message,
});
仍然没有排序我的异步问题。我现在正在看这篇文章,希望对此有所帮助:
How to return empty IQueryable in an async repository method
UPDATE-2
我想我已经弄明白了,但它仍然没有像我想的那样敏感,所以我不能100%确定它是否正确完成。我认为在我的WPF应用程序中交换到我的日志选项卡时,交换会是即时的,但事实并非如此!
无论如何,我已经改变了:
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();
}
}
如果有的话,代码肯定比我原来的代码简单。
更新-3:
当我这样称呼时:
return new PageList<LogEntity>(_dbContext.Logs, pageNumber, pageSize);
它返回TotalCount = 100,000,PageCount = 200,Page = 0,PageSize 500,但是当调用AddRange时它会抛出错误,即
类型&#39; System.NotSupportedException&#39;的例外情况发生在 EntityFramework.SqlServer.dll但未在用户代码中处理 附加信息:方法&#39; Skip&#39;仅支持排序 在LINQ to Entities中输入。方法&#39; OrderBy&#39;必须先叫 方法&#39; Skip&#39;。
所以我通过调用:
来解决这个问题return new PageList<LogEntity>(_dbContext.Logs.OrderBy(o=>o.DateTime),
pageNumber, pageSize);
当我试图打电话给@krillgar最简单的建议时,即
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);
我收到以下错误:
类型&#39; System.NotSupportedException&#39;的例外情况发生在 EntityFramework.SqlServer.dll但未在用户代码中处理 附加信息:实体或复杂类型 &#39; MyCompany.DataLayerSql.LogEntity&#39;不能在LINQ中构造 实体查询。
在PageList类中的this.TotalCount = source.Count();
。
有什么想法吗?
答案 0 :(得分:1)
您在这里错误地使用async
。除非您正在进行I / O或非常长的操作,否则通常只会在创建,管理和合并线程时产生额外的开销。
从数据库中查询是一项I / O操作,但是您还没有了解实体框架的行为,因此您不会错过使此操作异步的好处。
实体框架(和一般的LINQ)使用一种名为Deferred Execution的技术。在这种情况下,这意味着在您想要对数据执行操作之前,不会向您的数据库发送任何内容。您可以有条件地将.Where()
,.Skip()
等添加到您的内容中,而EF只会坐在那里准备构建SQL查询。
要将该SQL语句发送到数据库,您需要对其执行操作,这需要在PageList
构造函数中执行两次。第一个是:
TotalCount = source.Count();
这会使SQL包含所有WHERE
语句等,前缀为SELECT COUNT (*)
,并获取结果。
第二次来了:
AddRange(source.Skip(Page * PageSize).Take(PageSize).ToList());
在上一行的末尾,.ToList()
将向您的数据库发送另一个查询,检索您要求的所有列和行,并填充您的所有实体。 这个是您希望异步的地方,但是you can't make an async
constructor。
您的替代方案是放弃在构造函数中设置所有内容并使用方法,这可以很容易地生成async
。
在您的原始问题中,您从这开始:
_dbContext.Logs.Select(l => new
{
LogId = l.LogId,
Message = l.Message,
})
.OrderBy(o => o.DateTime)
您也已更新,将OrderBy()
和.ToPageList()
放在.Select()
之前。但是,您仍然将其作为匿名对象进行查询,因此您需要在需要之后继续进行投射。
回到问题的根源,我们需要查看你的return语句:
return await results.AsQueryable<LogEntity>().ToListAsync();
没有必要这样做,除了在那里放置一个人为的异步电话,它不会为你节省任何东西(见上文)。您投向.AsQueryable<T>()
只会增加处理效果,并且不会为您提供任何内容。
您使用所拥有内容的最简单方法是重新排列并消除冗余代码。您的.ToPageList()
已将对象投射为List<T>
,因此如果您按照正确的顺序执行操作,您将为自己留下很多悲伤:
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);
这就是你所需要的一切。
如果您在使用async
时已经死定,那么您应该通过添加默认构造函数和以下方法来重写您的类:
public async Task CreateAsync(IQueryable<T> source, int page, int pageSize)
{
TotalCount = await source.CountAsync(); // async here would help
PageCount = GetPageCount(pageSize, TotalCount);
Page = page < 1 ? 0 : page - 1;
PageSize = pageSize;
AddRange(await source.Skip(Page * PageSize)
.Take(PageSize)
.ToListAsync()); // async here too!
}
可以通过重构来清理,但这就是要点。然后这样称呼:
// Get your query set up, but don't execute anything on it yet.
var results = _dbContext.Logs.Select(l => new LogEntity
{
LogId = l.LogId,
l.Message
})
.OrderBy(l => l.DateTime);
var pageList = new PageList<LogEntity>();
await pageList.Create(results, pageNumber, pageSize);
return pageList;