2016-12-06 63 views
0

我是新來的EF,所以先進的道歉,如果事情沒有做到正確。我努力讓分頁與EF 6異步工作。異步尋呼與實體框架6.1.3

我已經按照這篇文章實現了分頁機制: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(); 
    } 
} 

當我運行上面,我得到:

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 .

我GOOGLE了錯誤,而我讀過許多文章,我仍然在努力得到它的工作。

任何人都可以告訴我如何解決這個問題,因爲我不知道從這個階段開始。

感謝

UPDATE-1

由於伊萬在他的評論中強調,我不認爲我需要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,頁頁次= 200,頁= 0,每頁500,但隨後引發錯誤

:當的AddRange被稱爲即

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'.

所以我通過調用這個固定

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); 

我得到以下錯誤:

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.

上的分頁類this.TotalCount = source.Count();

任何想法?

+0

看起來你不需要'PagedList'類,因爲你沒有使用它。另外2個'選擇'的原因是什麼? –

+0

@IvanStoev我正在使用它。它在GetLogsAsync函數的第10行中使用。至於2個選擇,這是一個很好的問題,我不想在這裏提出多個問題。我剛剛又做了一次,我簡化了它。我會在一秒鐘內上傳它。 – Thierry

+1

通過不使用它,我的意思是你沒有返回這個類的全部目的'PagedList '。如果您只需要分頁結果,只需在查詢中包含「Skip」/「Take」。例如返回await _dbContext.Logs.OrderBy(o => o.DateTime).Select(...)。Skip((pageNumber - 1)* pageSize).Take(pageSize).ToListAsync();' –

回答

0

您在這裏錯誤地使用了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; 
+0

感謝您的詳細解答。對不起,我遲遲沒有回來,但我正在努力處理我在問題中忽略的另一部分內容。我需要將LogEntity(數據庫模型)轉換爲Log對象(域模型)。我會盡快更新這一部分。 – Thierry

+0

當我嘗試你的'最簡單的'建議時,我得到以下錯誤:'實體或複雜類型'MyCompany.DataLayerSql.LogEntity'不能在LINQ to Entities查詢中構造。我錯過了什麼嗎? – Thierry

+0

該類有一個無參數的構造函數?這是一個C#類?如果你想最終的結果是一個'Log'對象,只需要替換它。 – krillgar