使用Entity Framework 6.1.3进行异步分页

时间:2016-12-06 12:18:10

标签: c# entity-framework asynchronous

我是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();

有什么想法吗?

1 个答案:

答案 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;