web-dev-qa-db-ja.com

クライアント側のGroupByはサポートされていません

次のEntity Framework Core 3.0クエリがあります。

var units = await context.Units
  .SelectMany(y => y.UnitsI18N)
  .OrderBy(y => y.Name)
  .GroupBy(y => y.LanguageCode)
  .ToDictionaryAsync(y => y.Key, y => y.Select(z => z.Name));

次のエラーが発生します。

Client side GroupBy is not supported.

クライアントまたはその一部でクエリを実行するには、次のようにします。

var units = context.Units
  .SelectMany(y => y.UnitsI18N)
  .OrderBy(y => y.Name)
  .AsEnumerable()
  .GroupBy(y => y.LanguageCode)
  .ToDictionary(y => y.Key, y => y.Select(z => z.Name));

今では動作します。

クライアントでクエリを実行していないのに、なぜこのエラーが発生するのですか?

28
Miguel Moura

.GroupBy(y => y.LanguageCode).ToDictionaryAsync(y => y.Key, y => y.Select(z => z.Name));はSQLに変換できません。 EF Core 3.0は例外をスローして、Unitsのすべてのレコードがグループ化されてディクショナリにマップされる前にデータベースからフェッチされることを確認します。

これは、EF Core 3.0の最大の変更です。 https://docs.Microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes

15
Thanh Nguyen

LINQ GroupByの機能とSQL _GROUP BY_でできることについて、よくある誤解があるようです。私はまったく同じ罠に陥り、最近これに頭を抱える必要があったので、この問題のより完全な説明を書くことにしました。


短い答え:

LINQ GroupByは、SQLの_GROUP BY_ステートメントとは大きく異なります:LINQだけ基になるコレクションをキーに応じてチャンクに分割し、SQLにさらに集約関数を適用してこれらのそれぞれを圧縮します単一の値にチャンクダウンします。

これが、EFがメモリ内でLINQのようなGroupByを実行する必要がある理由です。

EF Core 3.0より前では、これは暗黙的に行われていたため、EFはすべての結果行をダウンロードして、LINQ GroupByを適用していました。ただし、この暗黙の動作により、プログラマーはentireLINQクエリがSQLで実行され、結果セットがかなり大きい場合にパフォーマンスに多大な影響を与える可能性があることを期待できます。このため、GroupByの暗黙的なクライアント側の評価は EF Core 3.0では完全に無効化 でした。

これで、.AsEnumerable()または.ToList()などの関数を明示的に呼び出して、結果セットをダウンロードし、メモリ内LINQ操作を続行する必要があります。


長い答え:

次の表solvedExercisesは、この回答の実行例です。

_+-----------+------------+
| StudentId | ExerciseId |
+-----------+------------+
|         1 |          1 |
|         1 |          2 |
|         2 |          2 |
|         3 |          1 |
|         3 |          2 |
|         3 |          3 |
+-----------+------------+
_

このテーブルのレコード_X | Y_は、学生Xが演習Yを解決したことを示しています。

質問では、LINQのGroupByメソッドの一般的な使用例について説明します。コレクションを取得して、チャンクにグループ化します。各チャンクの行は共通のキーを共有します。

この例では、_Dictionary<int, List<int>>_を取得する場合があります。これには、各生徒の解答問題のリストが含まれています。 LINQでは、これは非常に簡単です。

_var result = solvedExercises
    .GroupBy(e => e.StudentId)
    .ToDictionary(e => e.Key, e => e.Select(e2 => e2.ExerciseId).ToList());
_

出力(完全なコードについては dotnetfiddle を参照):

_Student #1: 1 2 
Student #2: 2 
Student #3: 1 2 3 
_

これは、ListDictionaryを好きなだけネストできるため、C#データ型で簡単に表すことができます。

これをSQLクエリの結果として想像してみます。 SQLクエリの結果は通常、返される列を自由に選択できる表として表されます。上記のクエリをSQLクエリの結果として表すには、

  • 複数の結果テーブルを生成し、
  • グループ化された行を配列に入れる、または
  • どういうわけか「結果セットセパレータ」を挿入します。

私の知る限り、これらのアプローチは実際には実装されていません。多くても、MySQLの _GROUP_CONCAT_ のようなハックな回避策があります。これにより、結果の行を文字列に結合できます( relevant SO =回答 )。

したがって、SQLはLINQのGroupByの概念に一致する結果を生成できないことがわかります。

代わりに、SQLはいわゆるaggregationのみを許可します。たとえば、生徒が合格した演習の数を数えたい場合は、

_SELECT StudentId,COUNT(ExerciseId)
FROM solvedExercises
GROUP BY StudentId
_

...これにより、

_+-----------+-------------------+
| StudentId | COUNT(ExerciseId) |
+-----------+-------------------+
|         1 |                 2 |
|         2 |                 1 |
|         3 |                 3 |
+-----------+-------------------+
_

集計関数は、行のセットを1つの値(通常はスカラー)に減らします。例は、行数、合計、最大値、最小値、および平均です。

これはEF Coreによって実装されています:実行中

_var result = solvedExercises
    .GroupBy(e => e.StudentId)
    .Select(e => new { e.Key, Count = e.Count() })
    .ToDictionary(e => e.Key, e => e.Count);
_

上記のSQLを生成します。生成されたSQLクエリに使用する集計関数をEFに指示するSelectに注意してください。


要約すると、LINQ GroupBy関数はSQL _GROUP BY_ステートメントよりもはるかに一般的です。SQLの制限により、単一の2次元の結果テーブルのみを返すことができます。したがって、SQLの結果セットをダウンロードした後、質問のようなクエリとこの回答の最初の例をメモリで評価する必要があります。

暗黙的にこれを行う代わりに、EF Core 3.0では開発者 この場合は例外をスローすることを選択 ;これにより、小さなテストデータベースが原因で開発中に気付かれない可能性がある、数百万行の潜在的に大きなテーブル全体が誤ってダウンロードされるのを防ぎます。

2
Jan Wichelmann