前提・実現したいこと
現在、EntityFrameworkCoreを使用してDBへのアクセスを行う.NET Core MVCアプリを作成しています。
言語はC#です。
EF・MVCともに初めて触るもので、学習途中です。
データベースから、GroupBy
メソッドを使用して集計した値からその中央値を計算した結果を取得したいです。
イメージとしては、以下のようなテーブルに対して、
id | value |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
2 | 2 |
2 | 5 |
2 | 3 |
以下のようなLINQクエリを使ってデータを取得したいと考えています。
({id = 1,median = 2},{id=2,median=3}
のリストを取得するイメージです)
C#
var result = dbContext.table .GroupBy(item => item.id) .Select(item => new { id = item.Key, median = item.Median(elem => elem.value) }) .ToList();
また、中央値を求めるMedian<double?>
をIEnumerable<double?>
の拡張メソッドとして以下のように定義しています。
C#
public static double Median(this IEnumerable<double> src) { //対象のシーケンスを昇順にソート var sortedSeq = src.OrderBy(item => item).ToArray(); //シーケンスの数に応じて処理を分岐 if (sortedSeq.Count() % 2 == 0) { //偶数個 var retSeq = sortedSeq.Count() / 2; return (sortedSeq[retSeq - 1] + sortedSeq[retSeq]) / 2; } else { //奇数個 var retSeq = sortedSeq.Count() / 2; return sortedSeq[retSeq]; } } public static double? Median<T>(this IEnumerable<T> src, Func<T, int?> selector) { var srcArray = new double[src.Count()]; //IEnumrable<double?>に変換し、中央値を返す foreach (var item in src.Select((item, i) => new { Value = item, Seq = i })) { srcArray[item.Seq] = selector(item.Value).Value; } return srcArray.Median(); }
発生している問題・エラーメッセージ
以下のコードのようにアクセスしたときに、中央値を取得することができません。
idで集計し、その値から中央値を取得できることを期待しています。
C#
var result = dbContext.table .GroupBy(item => new { item.id , item.value }) .Select(item => new { id = item.id, median = item.Median(item => item.value ) }).ToList();
エラーコードは以下の通りです。
(実際のエラー内容をペーストしているので、上記イメージコードと内容が異なります)
System.InvalidOperationException: 'Processing of the LINQ expression 'GroupByShaperExpression: KeySelector: p.rank_id, ElementSelector:EntityShaperExpression: EntityType: PredictionDatum ValueBufferExpression: ProjectionBindingExpression: EmptyProjectionMember IsNullable: False ' by 'RelationalProjectionBindingExpressionVisitor' failed. This may indicate either a bug or a limitation in Entity Framework. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.'
試したこと
当初はエラー内容にある記事の内容から、GroupBy
をサーバー側で評価できないために発生しているのかと考えました。
しかし、標準で存在する平均値を求めるAverage
メソッドを使用すると、
問題なく取得することができました。
このことから、
- 拡張メソッドを定義してもLINQ To SQLでは使用できないのでは?
- SQLとして実行するには別の何かを定義する必要があるのでは?
などと考えましたが、有用な情報が得られませんでした。
(以下のコードはidごとに平均値を取得することができました)
C#
var result = dbContext .table .GroupBy(item => item.id) .Select(item => new { id = item.Key, average = item.Average(elem => elem.value) }) .ToList();
また、最初にAsEnumerable
メソッドを使用し、クライアント側での処理を行うことで問題なく取得できることがわかりました。
しかし、これではすべての値を一度クライアント側に取ってきた上で計算を行っていると思います。
SQLServer側で集計できる方法が知りたいです。
var result = dbContext .table .AsEnumerable() .GroupBy(item => item.id) .Select(item => new { id = item.Key, median = item.Median(elem => elem.value) });
補足情報(FW/ツールのバージョンなど)
- .NET Core 5 MVC
- C#
- EntityFrameworkCore 5.0.9
- Microsoft SQL Server Developer (64-bit) バージョン:15.0.4153.1
- Visual Studio 2019
での開発をしています。
質問内容のまとめ
改めて、質問内容をまとめて記載します。
- 自作した
IEnumerable<T>
の拡張メソッドでは、データベースへのアクセスに使用することはできないのでしょうか。 - 上記ができない場合、それを可能にする方法はあるでしょうか。
- 上記コードで、
Average
メソッドでは取得できるのに自作のMedian
では取得できない理由はどういったものでしょうか。
よろしくお願いします。
まだ回答がついていません
会員登録して回答してみよう