7

この質問から与えられたコード では、セレクター関数を渡すときにOrderByはSQLに変換されません

Func<Table1, string> f = x => x.Name;
var t = db.Table1.OrderBy(f).ToList();

翻訳されたSQLは次のとおりです。

SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[Name] AS [Name]
FROM [dbo].[Table1] AS [Extent1]

わかった。

コードがコンパイルされることを理解できます:IQueryableから継承します。これには、パラメーターとしてasIEnumerableをとるOrderByメソッドがあります。Func<TModel, TValue>

Expression<Func<TModel, TValue>>OrderByパラメーター(IQueryable用のパラメーター)としてを渡さなかったため、SQLでORDERBY句が生成されないことを理解できます。

しかし、舞台裏では何が起こりますか?「間違った」OrderByメソッドはどうなりますか?何もない ?どうして、なぜなのかわからない…夜の光は?

4

4 に答える 4

5

fは式ではなくデリゲートであるため、コンパイラはIEnumerable OrderBy拡張メソッドではなく拡張メソッドを選択しIQueryableます。

これは、すべての結果がデータベースからフェッチされることを意味します。これは、Linq to Objects のようにメモリ内で順序付けが行われるためです。つまり、メモリ内では、すべてのレコードをフェッチすることによってのみ順序付けを行うことができます。

もちろん、実際には、結果の列挙を開始するまで、これは実際には発生しませんToList()

コメントに応じて更新

あなたの質問は、あいまいさを導入するという観点から、IQueryable/二重性が「危険」であるということと同じくらいのようです。IEnumerableそれは本当にそうではありません:

t.OrderBy(r => r.Field)

C# はラムダを何よりも優先するものと見なすExpression<>ため、tが an のIQueryable場合はIQueryable拡張メソッドが選択されます。とオーバーロードを使用stringしてオーバーロードされたメソッドに渡される変数と同じです。バージョンが使用されるのは、それが最適な表現であるためです。stringobjectstring

Jeppeが指摘したように、実際には、継承されたインターフェースの前に即時インターフェースが使用されているためです

t.AsEnumerable().OrderBy(r => r.Field)

C# はIQueryableこれ以上 を認識できないため、ラムダを として扱います。これは、Func<A, B>次善の表現であるためです。(以前の私の/アナロジーobjectで利用できるメソッドのみに相当します。stringobject

そして最後にあなたの例:

Func<t, string> f = r => r.Field;
t.OrderBy(f);

開発者がデリゲートと式の違いを根本的に理解していない限り、このコードを記述している開発者がこれを低レベル コンポーネントの式として処理して SQL に変換することを期待できる方法はありません。その場合は、少し読むだけで問題が解決します。

開発者が新しいテクノロジの使用に着手する前に、少し読むことを要求するのは不合理だとは思いません。特に、MSDN の弁護において、この特定の主題が十分にカバーされている場合はなおさらです。

この編集を追加することで、以下の@IanNewsonによるコメントが無効になったことに気付きました-しかし、それが理にかなっている説得力のある議論を提供することを願っています:)

于 2012-08-31T20:25:15.973 に答える
3

しかし、舞台裏では何が起こりますか?

db.Table1を返すと仮定するとTable<Table1>、コンパイラは次のようになります。

  • Table<T>メソッドがあるかどうかを確認しますOrderBy-いいえ
  • 実装する基本クラスまたはインターフェースのいずれかにOrderByメソッドがあるかどうかを確認します-いいえ
  • 拡張メソッドの検討を開始します

ターゲットタイプに一致する拡張メソッドQueryable.OrderByとしてとの両方を検出しますが、メソッドは適用できないため、代わりに使用します。Enumerable.OrderByQueryable.OrderByEnumerable.OrderBy

したがって、コンパイラがコードを次のように書き直したかのように考えることができます。

List<Table1> t = Enumerable.ToList(Enumerable.OrderBy(db.Table1, f));

実行時に、Enumerable.OrderByソース()を反復処理しdb.Table1、キー抽出関数に基づいて適切な順序付けを実行します。(厳密に言えば、最初の結果を求められたときにソースを反復処理するをすぐに返します。)IEnumerable<T>

于 2012-08-31T20:24:55.267 に答える
2

queryableはすべてのレコードを返し(したがって、SQLステートメントにWHERE句はありません)、Funcは、を介してクライアントのメモリ内のオブジェクトに適用されますEnumerable.OrderBy。具体的には、パラメーターがFuncであるため、OrderBy呼び出しはEnumerable.OrderByに解決されます。したがって、静的メソッド呼び出し構文を使用してステートメントを書き直し、何が起こっているのかを少し明確にすることができます。

Func<Table1, string> f = x => x.Name; 
var t = Enumerable.OrderBy(db.Table1, f).ToList(); 

最終的に、OrderByで指定された並べ替えは、データベースサーバーではなく、クライアントプロセスによって実行されます。

于 2012-08-31T20:24:12.213 に答える
2

この回答は、Andras Zoltan の回答に対する一種のコメントです (ただし、これは長すぎてコメント形式に収まりません)。

Zoltan の答えは興味深いものであり、C# がラムダを何よりも重要な [...] と見なすExpression<>というフレーズを除いて、ほとんど正しいです。

Expression<>C# は、ラムダ (および任意の無名関数) をデリゲートおよびその同じデリゲートの (式ツリー) に等しく "近い" と見なします。C# の仕様によれば、どちらも「より良い変換ターゲット」ではありません。

したがって、次のコードを検討してください。

class C
{
    public void Overloaded(Expression<Func<int, int>> e)
    {
        Console.WriteLine("expression tree");
    }
    public void Overloaded(Func<int, int> d)
    {
        Console.WriteLine("delegate");
    }
}

それで:

var c = new C();
c.Overloaded(i => i + 1);   // will not compile! "The call is ambiguous ..."

したがって、それが機能する理由IQueryable<>は別のものです。ダイレクト インターフェイス タイプで定義されたメソッドは、基本インターフェイスで定義されたメソッドよりも優先されます。

説明のために、上記のコードを次のように変更します。

interface IBase
{
    void Overloaded(Expression<Func<int, int>> e);
}
interface IDerived : IBase
{
    void Overloaded(Func<int, int> d);
}
class C : IDerived
{
    public void Overloaded(Expression<Func<int, int>> e)
    {
        Console.WriteLine("expression tree");
    }
    public void Overloaded(Func<int, int> d)
    {
        Console.WriteLine("delegate");
    }
}

それで:

IDerived x = new C();
x.Overloaded(i => i + 1);  // compiles! At runtime, writes "delegate" to the console

ご覧のとおり、 で定義されたメンバーでIDerivedはなく、 で定義されたメンバーが選択されていIBaseます。( と比較してIQueryable<>) 状況を逆にしたことに注意してください。したがって、私の例では、デリゲート オーバーロードは最も派生したインターフェイスで定義されているため、式ツリー オーバーロードよりも優先されます。

注:問題IQueryable<>OrderByメソッドが通常のインスタンス メソッドではない場合。代わりに、1 つは派生インターフェイスの拡張メソッドであり、もう 1 つは基本インターフェイスの拡張メソッドです。しかし、説明は似ています。

于 2012-09-02T15:09:17.007 に答える