10

私はEF 4.3.1を使用しています... EF 4.x DbContext Generatorによって生成されたデータベースファーストのPOCOエンティティで4.4にアップグレードしたばかりです(問題は残ります) 。「Wiki」という名前の次のデータベースがあります(テーブルとデータを作成するSQLスクリプトはこちらです):

Author(ID, Name) <-- Article(AuthorID, Title, Revision, CreatedUTC, Body)

Wiki 記事が編集されると、レコードが更新される代わりに、新しいリビジョンが新しいレコードとして挿入され、リビジョン カウンターがインクリメントされます。私のデータベースには、「John Doe」という 1 人の著者がいて、「Article A」と「Article B」という 2 つの記事があり、記事 A には 2 つのバージョン (1 と 2) がありますが、記事 B には 1 つのバージョンしかありません。

ここに画像の説明を入力

遅延読み込みとプロキシ作成の両方を無効にしています (これは、LINQPad で使用しているサンプル ソリューションです)。名前が「John」で始まる人が作成した記事の最新版を取得したいので、次のクエリを実行します。

Authors.Where(au => au.Name.StartsWith("John"))
       .Select(au => au.Articles.GroupBy(ar => ar.Title)
                                .Select(g => g.OrderByDescending(ar => ar.Revision)
                                              .FirstOrDefault()))

これは間違った結果を生成し、最初の記事のみを取得します。

ここに画像の説明を入力

.FirstOrDefault()次のクエリの.Take(1)結果に置き換えることにより、クエリに小さな変更を加えます。

Authors.Where(au => au.Name.StartsWith("John"))
       .Select(au => au.Articles.GroupBy(ar => ar.Title)
                                .Select(g => g.OrderByDescending(ar => ar.Revision)
                                              .Take(1)))

驚くべきことに、このクエリは正しい結果を生成します (ただし、ネストが増えます)。

ここに画像の説明を入力

私は、EF がわずかに異なる SQL クエリを生成していると想定しました。一方は 1 つの記事の最新リビジョンのみを返し、もう一方はすべての記事の最新リビジョンを返します。2 つのクエリによって生成された見苦しい SQL は、わずかに異なるだけです (比較: .FirstOrDefault () の SQL と .Take( 1) のSQL を比較してください)が、どちらも正しい結果を返します。

.FirstOrDefault()

ここに画像の説明を入力

.Take(1)(比較しやすいように列の順序を並べ替えています)

ここに画像の説明を入力

したがって、原因は生成された SQL ではなく、EF による結果の解釈です。EF が最初の結果を 1 つのArticleインスタンスに解釈し、2 番目の結果を 2 つのArticleインスタンスとして解釈するのはなぜですか? 最初のクエリが間違った結果を返すのはなぜですか?

編集: Connect に関するバグ レポートを開きました。この問題を修正することが重要だと思われる場合は、賛成票を投じてください。

4

4 に答える 4

3

見て:
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.firstordefault
http://msdn.microsoft.com/en-us/library/bb503062.aspx
非常に素晴らしい説明がありますTake がどのように機能するか (lazy、初期の brekaing) が、FirstOrDefault はありません。さらに、Take の説明を見て、Take を使用したクエリは、lazy をエミュレートしようとするために行数を削減する可能性があると「推測」します。 SQLでの評価であり、あなたのケースはそれが逆であることを示しています! なぜあなたがそのような効果を観察しているのか理解できません。

それはおそらく実装固有のものです..私にとっては、Take(1) と FirstOrDefault の両方が のようTOP 1に見えるかもしれませんが、機能的な観点からは、それらの「遅延」にわずかな違いがあるかもしれません: 1 つの関数がすべての要素を評価し、最初に戻ることがあります。 、2番目は最初に評価してから返し、評価を中断する場合があります..これは、何が起こったのかについての「ヒント」にすぎません。私にとってはナンセンスです。なぜなら、この件に関するドキュメントは見当たりませんし、一般的に、Take/FirstOrDefault の両方が遅延しており、最初の N 要素のみを評価する必要があると確信しているからです。

クエリの最初の部分で、 group.Select+orderBy+TOP1 は、グループごとの列で最大の「値」を持つ単一の行に関心があることを「明確に示しています」ですが、実際には簡単な方法はありませんSQL でそれを宣言する必要があるため、SQL エンジンと EF エンジンのどちらについても、その兆候はそれほど明確ではありません。

私に関して言えば、あなたが提示した動作は、FirstOrDefault が EF トランスレータによって内部クエリの 1 層上に「伝播」されたことを示している可能性があります。 OrderBy? :) ) - そしてそれはバグでしょう。

しかし -

違いは実行の意味や順序のどこかにあるはずなので、クエリの意味について EF が推測できることを見てみましょう。Author エンティティが記事を取得する方法 EF はどの記事を作成者にバインドするかをどのように知るのでしょうか? もちろん、nav プロパティ。しかし、一部の記事だけがプリロードされているのはどうしてでしょうか? 単純に見えます - クエリはいくつかの結果をいくつかの列で返します。列は著者全体と記事全体を記述しているので、それらを著者と記事にマップし、nav キーでそれらを互いに一致させます。わかった。しかし、それに複雑なフィルタリングを追加します..?

by-dateのような単純なフィルターで、単一のサブクエリですすべての記事について、行は日付で切り捨てられ、すべての行が消費されます。しかし、いくつかの中間順序付けを使用し、記事のいくつかのサブセットを生成する複雑なクエリを作成するのはどうでしょうか? 結果の作成者にバインドする必要があるサブセットはどれですか? それらすべての連合?それはすべての最上位のwhere-like句を無効にします。それらの最初の?ナンセンスですが、最初のサブクエリは中間ヘルパーになる傾向があります。そのため、おそらくクエリが、nav プロパティの部分読み込みのデータソースとしてすべて取得できる同様の構造を持つ一連のサブクエリと見なされる場合、ほとんどの場合、最後のサブクエリのみが実際の結果として取得されます。これはすべて抽象的な考え方ですが、Take() と FirstOrDefault の対比、および Join と LeftJoin の全体的な意味によって、実際には結果セットのスキャンの順序が変わる可能性があることに気付きました。for each author * for each title-group * select top one and check count and substitue for nullこれは、各著者ごとに小さな 1 アイテムの記事のコレクションを何度も生成したため、最後にアクセスしたタイトル グループからのみ得られる 1 つの結果になりました。

これは、明らかな「バグ」を除いて、私が考えることができる唯一の説明です。叫ぶ。LINQ ユーザーの私にとって、これはまだバグです。このような最適化がまったく行われていないか、FirstOrDef も含まれている必要があります (Take(1).DefaultIfEmpty() と同じであるため)。ところで、試してみましたか?私が言ったように、Take(1) は JOIN/LEFTJOIN の意味により FirstOrDefault と同じではありませんが、Take(1).DefaultIfEmpty() は実際には意味的に同じです。SQL でどのような SQL クエリが生成され、EF レイヤーでどのような結果が得られるかを見るのは楽しいかもしれません。

部分ロードでの関連エンティティの選択は私には決して明確ではなかったことを認めなければなりません。結果とグループ化が明示的に定義されるようにクエリを常に述べたように、実際には部分ロードを長い間使用していませんでした(*).. したがって、その内部動作のいくつかの重要な側面/ルール/定義を単に忘れていた可能性があります。実際には、結果セットからすべての関連レコードを選択する必要があります (ここで説明した最後のサブコレクションだけではありません)。私が何かを忘れていたら、私が今説明したことはすべて明らかに間違っているでしょう.

(*) あなたの場合、Article.AuthorID も nav-property にし (public Author Author get set)、次のように、よりフラット/パイプライン化されるようにクエリを書き直します。

var aths = db.Articles
              .GroupBy(ar => new {ar.Author, ar.Title})
              .Take(10)
              .Select(grp => new {grp.Key.Author, Arts = grp.OrderByDescending(ar => ar.Revision).Take(1)} )

次に、著者を部分的に埋めて著者のみを使用しようとするのではなく、著者と芸術のペアでビューを別々に埋めます。ところで。EF と SServer に対してはテストしていません。これは、JOIN の場合に「クエリを逆さまにする」およびサブクエリを「フラット化する」例に過ぎず、LEFTJOIN では使用できません。記事のない著者は、元のクエリのように著者から開始する必要があります..

これらのゆるい考えが「なぜ」を見つけるのに少し役立つことを願っています..

于 2012-08-27T10:01:46.683 に答える
2

FirstOrDefault()メソッドは即時ですが、他のメソッド ( Take(int)) は実行まで延期されます。

于 2012-08-27T17:59:53.950 に答える
0

前の回答のように、私は問題について推論しようとしました-私は辞任し、別のものを書いています:)もう一度見た後、それはバグだと思います。Take and post the case to Microsoft's Connect を使用して、Microsoft の Connect が何を言っているのかを確認するだけでよいと思います。

私が見つけたものは次のとおりです

「Microsoft 2011-09-22 at 16:07」からの応答では、EF 内のいくつかの最適化メカニズムについて詳しく説明しています。いくつかの場所で、スキップ/テイク/オーダーバイの並べ替えについて述べており、ロジックが一部の構造を認識しないことがあります..「オーダーバイリフティング」でまだ適切に分岐されていない別のコーナーケースに遭遇したと思います. 全体として、結果の SQL では、order-by 内に select-top-1 があり、損傷は「トップ 1」を 1 レベル上げすぎたように見えます!

于 2012-08-27T16:44:36.140 に答える