質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Entity Framework

Entity Frameworkは、.NET Framework 3.5より追加されたデータアクセス技術。正式名称は「ADO.NET Entity Framework」です。データベースエンジンに依存しておらず、データプロバイダの変更のみで様々なデータベースに対応できます。

.NET Core

.NET Coreは、マネージソフトウェアフレームワークでオープンソースで実装されています。クロスプラットフォームを前提に考えられており、Windows/Mac/Linuxで動くアプリケーションを作成することが可能です。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

SQL Server

SQL Serverはマイクロソフトのリレーショナルデータベース管理システムです。データマイニングや多次元解析など、ビジネスインテリジェンスのための機能が備わっています。

LINQ to SQL

LINQ to SQLは.NET Framework 3.5のコンポーネントで、リレーショナル データをオブジェクトとして管理するためのランタイム インフラストラクチャを提供します。

Q&A

解決済

1回答

2677閲覧

EntityFrameworkCoreでの自作拡張メソッドの使用方法

yanelmo

総合スコア4

Entity Framework

Entity Frameworkは、.NET Framework 3.5より追加されたデータアクセス技術。正式名称は「ADO.NET Entity Framework」です。データベースエンジンに依存しておらず、データプロバイダの変更のみで様々なデータベースに対応できます。

.NET Core

.NET Coreは、マネージソフトウェアフレームワークでオープンソースで実装されています。クロスプラットフォームを前提に考えられており、Windows/Mac/Linuxで動くアプリケーションを作成することが可能です。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

SQL Server

SQL Serverはマイクロソフトのリレーショナルデータベース管理システムです。データマイニングや多次元解析など、ビジネスインテリジェンスのための機能が備わっています。

LINQ to SQL

LINQ to SQLは.NET Framework 3.5のコンポーネントで、リレーショナル データをオブジェクトとして管理するためのランタイム インフラストラクチャを提供します。

0グッド

0クリップ

投稿2021/09/14 10:58

前提・実現したいこと

現在、EntityFrameworkCoreを使用してDBへのアクセスを行う.NET Core MVCアプリを作成しています。
言語はC#です。
EF・MVCともに初めて触るもので、学習途中です。

データベースから、GroupByメソッドを使用して集計した値からその中央値を計算した結果を取得したいです。
イメージとしては、以下のようなテーブルに対して、

idvalue
11
12
13
22
25
23

以下のようなLINQクエリを使ってデータを取得したいと考えています。
({id = 1,median = 2},{id=2,median=3}のリストを取得するイメージです)

C#

1 2var result = dbContext.table 3.GroupBy(item => item.id) 4.Select(item => new 5{ 6 id = item.Key, 7 median = item.Median(elem => elem.value) 8}) 9.ToList(); 10

また、中央値を求めるMedian<double?>IEnumerable<double?>の拡張メソッドとして以下のように定義しています。

C#

1 2public static double Median(this IEnumerable<double> src) 3{ 4 //対象のシーケンスを昇順にソート 5 var sortedSeq = src.OrderBy(item => item).ToArray(); 6 7 //シーケンスの数に応じて処理を分岐 8 if (sortedSeq.Count() % 2 == 0) 9 { 10 //偶数個 11 var retSeq = sortedSeq.Count() / 2; 12 return (sortedSeq[retSeq - 1] + sortedSeq[retSeq]) / 2; 13 } 14 else 15 { 16 //奇数個 17 var retSeq = sortedSeq.Count() / 2; 18 return sortedSeq[retSeq]; 19 } 20} 21 22public static double? Median<T>(this IEnumerable<T> src, Func<T, int?> selector) 23{ 24 var srcArray = new double[src.Count()]; 25 26 //IEnumrable<double?>に変換し、中央値を返す 27 foreach (var item in src.Select((item, i) => new { Value = item, Seq = i })) 28 { 29 srcArray[item.Seq] = selector(item.Value).Value; 30 } 31 32 return srcArray.Median(); 33} 34

発生している問題・エラーメッセージ

以下のコードのようにアクセスしたときに、中央値を取得することができません。
idで集計し、その値から中央値を取得できることを期待しています。

C#

1 2var result = dbContext.table 3.GroupBy(item => new { item.id , item.value }) 4.Select(item => new { 5 id = item.id, 6 median = item.Median(item => item.value ) 7}).ToList(); 8

エラーコードは以下の通りです。
(実際のエラー内容をペーストしているので、上記イメージコードと内容が異なります)

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#

1var result = dbContext 2.table 3.GroupBy(item => item.id) 4.Select(item => new 5{ 6 id = item.Key, 7 average = item.Average(elem => elem.value) 8}) 9.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

での開発をしています。

質問内容のまとめ

改めて、質問内容をまとめて記載します。

  1. 自作したIEnumerable<T>の拡張メソッドでは、データベースへのアクセスに使用することはできないのでしょうか。
  2. 上記ができない場合、それを可能にする方法はあるでしょうか。
  3. 上記コードで、Averageメソッドでは取得できるのに自作のMedianでは取得できない理由はどういったものでしょうか。

よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

gentaro

2021/09/14 11:56

その疑問に対する情報はエラー・メッセージのリンクを読めばわかるんじゃない? > This may indicate either a bug or a limitation in Entity Framework. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.' 読んで質問してるのかどうか知らんけど。
yanelmo

2021/09/14 13:36

gentaro さん すみません、このリンクは読んだ上での質問でした。 が、ご指摘受けて改めて読んだところ理解していなかった箇所がありました。 「サポートされていないクライアント評価」で書いてあるように、上記の自分のコードではSelectメソッド内でMedianメソッドが使用されているため、SQLに変換できず実行時エラーになっていると考えています。 そのため、 1:SQLに変換できないため不可能 3:標準の集計関数はSQLへの変換が可能だから という結論になりました。 ここまでは合っているでしょうか。 2の、自作のメソッドを使えるようにする方法があるか、ないかというのはわかりませんでした。 読む限りでは、AsEnumerableメソッドを使用してクライアント側で評価しなさいということでしょうか。
退会済みユーザー

退会済みユーザー

2021/09/14 13:48

Linq to Entities は、Linq to Objects とは違って、そのクエリ式が SQL Server などの DB で使われる SQL に変換できる必要があるという話ではないかと言っているのですが、話を聞いてもらってます?
yanelmo

2021/09/14 13:50 編集

SurferOnWww さん ありがとうございます。 EntitiesとObjectの違いで間違いないです。 「SQLに変換できる必要がある」という点が質問時点で分かっていませんでした。 リンクのCodeZineの記事も読みました。非常に参考になりました。 できるか、できないかを知りたいのですが、こういった自作メソッドをEFで評価できるように実装するというのは可能なのでしょうか。 (追記) すみません、リンク先の確認に時間かかっていました。 そういったお話で間違いないです。
退会済みユーザー

退会済みユーザー

2021/09/14 13:59

紹介した記事にも書きましたが、LINQ to Entities クエリで使用する SQL Server 関数を公開する SqlFunctions クラスのメソッドに該当するものがあれば、それを使うという手もあるかもしれません・・・が、自作メソッドとか、それ以前に EF Core ではダメかも。 ダメなら AsEnumerable なり Tolist なりで Object にしてそれに Linq を適用することを考える方が良いかもしれません。
yanelmo

2021/09/14 14:23

SqlFunctionsクラスにはありませんでした… もう少し調べていると、DB側に定義した関数をLinq to Entities中から呼び出すことができるというものがありました。 他に呼び出す方法はなさそうですので、一旦これで解決とさせていただきます。 回答ありがとうございました。 https://docs.microsoft.com/ja-jp/dotnet/framework/data/adonet/ef/language-reference/how-to-call-custom-database-functions
guest

回答1

0

自己解決

今回のエラーは、"LINQ to Entities"と"LINQ to Objects"を混同していたことにありました。
Entitiesでは、操作が全てSQLに変換できなければなりません。
しかしMedian拡張メソッドは変換できないため、実行時エラーとなります。

対応としては、AsEnumerableメソッドなどで一旦データを取得し、LINQ to Objectsとして呼び出すこととします。

【解決内容】

  1. 自作したIEnumerable<T>の拡張メソッドでは、データベースへのアクセスに使用することはできないのでしょうか。

→SQL文に変換できないため、使用できない。

2. 上記ができない場合、それを可能にする方法はあるでしょうか。
→一旦オブジェクトを取得してそれに対して呼び出す、またはカスタムデータベース関数として呼び出す(未確認です)。

  1. 上記コードで、Averageメソッドでは取得できるのに自作のMedianでは取得できない理由はどういったものでしょうか。

→1と同じく、SQL文に変換できないため。

【カスタム データベース関数を呼び出す】
https://docs.microsoft.com/ja-jp/dotnet/framework/data/adonet/ef/language-reference/how-to-call-custom-database-functions

ご回答いただいたgentaroさん、SurferOnWwwさん ありがとうございました。

投稿2021/09/14 14:34

yanelmo

総合スコア4

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問