2016-12-06 4 views
0

EFの新機能です。私はページングがEF 6と非同期で動作するように苦労しています。How to Increase the Performance of Entity Framework with Paging私はページングメカニズムを実装しました:How to Increase the Performance of Entity Framework with Paging、私はきれいだったと思っていました。これは非同期で動作するようになっていますが、これは問題です。記事を1としてエンティティフレームワークを使用した非同期ページング6.1.3

、私はインターフェイスを作成しました:

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

をそれはトータルカウント= 100,000 PAGECOUNT = 200、ページ= 0、のPageSize 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); 

私はすなわち

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

krillgarの最も簡単な提案@呼び出そうとすると、私は次のエラーを取得:PageListクラスでthis.TotalCount = source.Count();

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.

を。

アイデア?

+0

あなたがそれを使用していないため、あなたは 'PagedList'クラスは必要ないように見えます。また、2つの「選択」の理由は何ですか? –

+0

@IvanStoev私はそれを使用しています。 GetLogsAsync関数の10行目で使用されています。 2つの選択については、それは良い質問です。私はここで複数の質問をしたくありませんでした。私はちょうど別のものを持ってきて、それを単純化しました。私は秒でそれをアップロードします。 – Thierry

+1

これを使用しないと、そのクラスの目的全体である 'PagedList 'を返さないということです。単純にページングされた結果が必要な場合は、単純にクエリに 'Skip' /' Take'を含めてください。例えば'().Select(...)。Skip((pageNumber - 1)* pageSize).Take(pageSize).ToListAsync();' –

答えて

0

ここではasyncが間違っています。 I/Oや非常に長い操作をしている場合を除き、通常はスレッドの作成、管理、マージの際に余分なオーバーヘッドが発生します。

データベースからのクエリはI/O操作ですが、Entity Frameworkの動作が分からないため、この操作を非同期にする利点がありません。

エンティティフレームワーク(および一般的なLINQ)では、Deferred Executionという技術を使用しています。この場合、データを処理するまでは何もデータベースに送信されません。条件付きで.Where().Skip()などを心臓のコンテンツに追加することができ、EFはSQLクエリを作成する準備をするだけで座ります。

このSQL文をデータベースに送信するには、になる必要があります。これは、PageListのコンストラクタで2回行います。最初は、次のとおりです。あなたのWHERE文などのすべてとSQLを取る

TotalCount = source.Count(); 

SELECT COUNT (*)を付加し、その結果をフェッチします。

二度目はここにある:上記の行の終わりに

AddRange(source.Skip(Page * PageSize).Take(PageSize).ToList()); 

.ToList()はあなたのすべてを、データベースに別のクエリを送信しますが求めるすべての列と行を取得し、移入しますエンティティ。 このはあなたの非同期を希望しますが、you can't make an async constructorです。

あなたの代わりにコンストラクタ内のすべてを設定する代わりに、簡単にasyncにすることができますメソッドを使用することです。あなたの元の質問に

は、あなたがこれを開始しました:あなたはまた、以来、あなたの.Select()OrderBy().ToPageList()を置くために更新した

_dbContext.Logs.Select(l => new 
    { 
     LogId = l.LogId, 
     Message = l.Message, 
    }) 
    .OrderBy(o => o.DateTime) 

。しかし、依然として匿名オブジェクトとしてクエリを実行しているので、必要がある場合はもう一度キャストを続ける必要があります。戻ってあなたの問題の根本に行く

、私たちはあなたの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

関連する問題