4
        var numbers = new int[] { 1, 2, 3, 4, 5 };

        var contacts = from c in context.Contacts
                       where c.ContactID == numbers.Max() | c.ContactID == numbers.FirstOrDefault()
                       select c;

        foreach (var item in contacts) Console.WriteLine(item.ContactID); ;

Linq-to-Entitiesクエリは、最初にLinq式ツリーに変換され、次にObjectServicesによってコマンドツリーに変換されます。また、Linq-to-EntitiesクエリがLinq-to-Objectsクエリをネストしている場合、このネストされたクエリも式ツリーに変換されます。

a)ネストされたLinq-to-Objectsクエリの演算子は実際には実行されないと思いますが、代わりに特定のDB(またはおそらくObject Services)のデータプロバイダーはLinq-to-Objects演算子のロジックを適切なSQLに変換する方法を知っていますステートメント?

b)データプロバイダーは、一部のLinq-to-Objects演算子に対してのみ同等のSQLステートメントを作成する方法を知っていますか?

c)同様に、データプロバイダーは、Net Frameworkクラスライブラリの一部の非Linqメソッドに対してのみ同等のSQLステートメントを作成する方法を知っていますか?


ADAM MILLSへの返信:

1)あなたの返事に少し混乱しています。b)に返信して、SQL ServerのLinq2Entitiesデータプロバイダーが特定のLinq-to-Objects演算子をサポートしている場合、それを同等のSQLステートメントに変換しようとすることに同意し、c)に返信して、プロバイダーは特定の非Linqメソッドをサポートし、それを同等のSQLステートメントに変換します(サポートしていない場合は、例外をスローします)。しかし、a)の場合、 c)の場合とは正反対の応答をしたため、このプロバイダーはMax同等のSqlステートメントに変換しようとせず、代わりにそれを実行して、クエリで戻り値を使用しますか?

2)とにかく、私はいくつかのSQLしか知らないので完全にはわかりませんが、上記のコードに対して生成されたSQLクエリを読むと、データプロバイダーは実際にはメソッドを実行しなかったようですが、代わりに最大値を返すはずnumbers.Maxであることがどういうわけかわかりました次に、生成されたSQLクエリにTSQLの組み込みMAX関数numbers.Maxへの呼び出しを含めます。また、配列が保持するすべての値をSQLクエリに入れます。numbers

 SELECT CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN '0X0X'
         ELSE '0X1X'
       END                      AS [C1],
       [Extent1].[ContactID]    AS [ContactID],
       [Extent1].[FirstName]    AS [FirstName],
       [Extent1].[LastName]     AS [LastName],
       [Extent1].[Title]        AS [Title],
       [Extent1].[AddDate]      AS [AddDate],
       [Extent1].[ModifiedDate] AS [ModifiedDate],
       [Extent1].[RowVersion]   AS [RowVersion],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[CustomerTypeID]
       END                      AS [C2],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[InitialDate]
       END                      AS [C3],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[PrimaryDesintation]
       END                      AS [C4],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[SecondaryDestination]
       END                      AS [C5],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[PrimaryActivity]
       END                      AS [C6],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[SecondaryActivity]
       END                      AS [C7],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[Notes]
       END                      AS [C8],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[RowVersion]
       END                      AS [C9],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[BirthDate]
       END                      AS [C10],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[HeightInches]
       END                      AS [C11],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[WeightPounds]
       END                      AS [C12],
       CASE
         WHEN (([Project1].[C1] = 1)
               AND ([Project1].[C1] IS NOT NULL)) THEN [Project1].[DietaryRestrictions]
       END                      AS [C13]
FROM   [dbo].[Contact] AS [Extent1]
       LEFT OUTER JOIN (SELECT [Extent2].[ContactID]            AS [ContactID],
                               [Extent2].[BirthDate]            AS [BirthDate],
                               [Extent2].[HeightInches]         AS [HeightInches],
                               [Extent2].[WeightPounds]         AS [WeightPounds],
                               [Extent2].[DietaryRestrictions]  AS [DietaryRestrictions],
                               [Extent3].[CustomerTypeID]       AS [CustomerTypeID],
                               [Extent3].[InitialDate]          AS [InitialDate],
                               [Extent3].[PrimaryDesintation]   AS [PrimaryDesintation],
                               [Extent3].[SecondaryDestination] AS [SecondaryDestination],
                               [Extent3].[PrimaryActivity]      AS [PrimaryActivity],
                               [Extent3].[SecondaryActivity]    AS [SecondaryActivity],
                               [Extent3].[Notes]                AS [Notes],
                               [Extent3].[RowVersion]           AS [RowVersion],
                               cast(1 as bit)                   AS [C1]
                        FROM   [dbo].[ContactPersonalInfo] AS [Extent2]
                               INNER JOIN [dbo].[Customers] AS [Extent3]
                                 ON [Extent2].[ContactID] = [Extent3].[ContactID]) AS [Project1]
         ON [Extent1].[ContactID] = [Project1].[ContactID]
       LEFT OUTER JOIN (SELECT TOP (1) [c].[C1] AS [C1]
                        FROM   (SELECT [UnionAll3].[C1] AS [C1]
                                FROM   (SELECT [UnionAll2].[C1] AS [C1]
                                        FROM   (SELECT [UnionAll1].[C1] AS [C1]
                                                FROM   (SELECT 1 AS [C1]
                                                        FROM   (SELECT 1 AS X) AS [SingleRowTable1]
                                                        UNION ALL


                                                        SELECT 2 AS [C1]
                                                        FROM   (SELECT 1 AS X) AS [SingleRowTable2]) AS [UnionAll1]
                                                UNION ALL


                                                SELECT 3 AS [C1]
                                                FROM   (SELECT 1 AS X) AS [SingleRowTable3]) AS [UnionAll2]
                                        UNION ALL


                                        SELECT 4 AS [C1]
                                        FROM   (SELECT 1 AS X) AS [SingleRowTable4]) AS [UnionAll3]
                                UNION ALL


                                SELECT 5 AS [C1]
                                FROM   (SELECT 1 AS X) AS [SingleRowTable5]) AS [c]) AS [Limit1]
         ON 1 = 1
       LEFT OUTER JOIN (SELECT TOP (1) [c].[C1] AS [C1]
                        FROM   (SELECT [UnionAll7].[C1] AS [C1]
                                FROM   (SELECT [UnionAll6].[C1] AS [C1]
                                        FROM   (SELECT [UnionAll5].[C1] AS [C1]
                                                FROM   (SELECT 1 AS [C1]
                                                        FROM   (SELECT 1 AS X) AS [SingleRowTable6]
                                                        UNION ALL


                                                        SELECT 2 AS [C1]
                                                        FROM   (SELECT 1 AS X) AS [SingleRowTable7]) AS [UnionAll5]
                                                UNION ALL


                                                SELECT 3 AS [C1]
                                                FROM   (SELECT 1 AS X) AS [SingleRowTable8]) AS [UnionAll6]
                                        UNION ALL


                                        SELECT 4 AS [C1]
                                        FROM   (SELECT 1 AS X) AS [SingleRowTable9]) AS [UnionAll7]
                                UNION ALL


                                SELECT 5 AS [C1]
                                FROM   (SELECT 1 AS X) AS [SingleRowTable10]) AS [c]) AS [Limit2]
         ON 1 = 1
       CROSS JOIN (SELECT MAX([UnionAll12].[C1]) AS [A1]
                   FROM   (SELECT [UnionAll11].[C1] AS [C1]
                           FROM   (SELECT [UnionAll10].[C1] AS [C1]
                                   FROM   (SELECT [UnionAll9].[C1] AS [C1]
                                           FROM   (SELECT 1 AS [C1]
                                                   FROM   (SELECT 1 AS X) AS [SingleRowTable11]
                                                   UNION ALL


                                                   SELECT 2 AS [C1]
                                                   FROM   (SELECT 1 AS X) AS [SingleRowTable12]) AS [UnionAll9]
                                           UNION ALL


                                           SELECT 3 AS [C1]
                                           FROM   (SELECT 1 AS X) AS [SingleRowTable13]) AS [UnionAll10]
                                   UNION ALL


                                   SELECT 4 AS [C1]
                                   FROM   (SELECT 1 AS X) AS [SingleRowTable14]) AS [UnionAll11]
                           UNION ALL


                           SELECT 5 AS [C1]
                           FROM   (SELECT 1 AS X) AS [SingleRowTable15]) AS [UnionAll12]) AS [GroupBy1]
WHERE  [Extent1].[ContactID] IN ([GroupBy1].[A1], (CASE
                                                     WHEN ([Limit1].[C1] IS NULL) THEN 0
                                                     ELSE [Limit2].[C1]
                                                   END))

これに基づいて、Linq2Entitiesプロバイダーが実際に非LinqおよびLinq-to-Objectメソッドを実行せず、代わりにそれらの一部に対して同等のSQLステートメントを作成する(および他の場合は例外をスローする)可能性はありますか?


2番目の編集:

わかりました、私はあなたが私に言ったことをしました:

b)の場合、Linq-to-Objects拡張メソッドを作成しました。

public static class TEST_CLASS
{
    public static int Testing<TSource>(this IEnumerable<TSource> source)
    {
        Console.WriteLine("Testing Called"); // here I've put a breakpoint
        return source.Count();
    }
}

        List<int> list = new List<int>() {1,2,3,4,5,6 };

        var contact = (from c in context.Contacts
                       where c.ContactID == list.Testing()
                       select c).First();

コードをデバッグモードで実行すると、すぐに次の例外が発生します(したがって、デバッガーは例外をスローする前にTestingメソッドにステップインしません)。

System.NotSupportedException:LINQ to Entitiesは、メソッド'Int32 TestingInt32'メソッドを認識せず、このメソッドをストア式に変換できません。

c)非Linqメソッドを作成しました:

public class Another_TEST_CLASS
{
    public static int Testing_Again()
    {
        Console.WriteLine("Testing_Again called");// here I've put a breakpoint
        return 1000;
    }
}


        var contact = (from c in context.Contacts
                       where c.ContactID == Another_TEST_CLASS.Testing_Again()
                       select c).First();

コードをデバッグモードで実行すると、すぐに次の例外が発生します(したがって、デバッガーは例外をスローする前にTesting_Againメソッドにステップインしません)。

System.NotSupportedException:LINQ to Entitiesは、メソッド'Int32 Testing_Again()'メソッドを認識せず、このメソッドをストア式に変換できません。System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.Defaultで

前もって感謝します

4

2 に答える 2

4

LinqPadでこれを試したところ、生成されたSQLは次のようになります。

-- Region Parameters
DECLARE @p0 Int = 5
DECLARE @p1 Int = 1
-- EndRegion
SELECT [t0].[ContactID], [t0].[Name]
FROM [Contacts] AS [t0]
WHERE ([t0].[ContactID] = @p0) OR ([t0].[ContactID] = @p1)

これはLinq-to-Sqlを使用していますが、Linq-to-Entitiesが何か違うことをすることはないと思います。プロバイダーはLinq-to-Objectsクエリを実行し、結果を式ツリーに挿入します。


編集

Linq-to-Entitiesが、サーバー上でnumbers.Max()およびを評価するためのクエリを生成しているようです。numbers.FirstOrDefault()そのようにするのは非常に非効率的で、バグのように感じます。L2Sの動作よりもL2Eの動作の方が望ましいシナリオは考えられません。

クエリの外部に関連する値を抽出することで、L2Sの動作を強制できます。

var numbers = new int[] { 1, 2, 3, 4, 5 };

int max = numbers.Max();
int first = numbers.FirstOrDefault();

var contacts = from c in context.Contacts
   where c.ContactID == max !| c.ContactID == first
   select c;
于 2012-11-07T18:27:23.073 に答える
3

編集:申し訳ありませんが、この回答は、Linq-To-Entitiesとは異なるデータプロバイダーと異なるセマンティクスを持つLinq-To-SQLに対するものです。

式ツリーを基になるストアが理解できる形式(この場合はSQL)に変換するのはデータプロバイダーの責任であり、独自のルールを定義します。

Linq-To-LDAPプロバイダーの簡単な実装については、こちらをご覧ください

Linq-Sqlデータプロバイダーの場合、where句の値が必要であることを認識しています。式に基づいて、述語パラメーター(この例ではc)を使用して、照会されたテーブルから述語のどの部分が取得されるかがわかります。等式の反対側は、パラメーターとして渡す値またはSQL(関数、クエリ)のいずれかである必要があります。

値式の結果タイプが既知のSQLタイプであり、それがオブジェクト参照式(またはオブジェクト参照のメソッド呼び出し)から派生している場合、式は展開(実行)され、値はパラメーターとしてクエリに渡されます。

式が直接メソッド参照である場合、SQL関数またはthrowに一致させようとします(つまり、これはthrowになります)。

 where c.ContactID == Test()

式の結果がIQueryableの場合、SQLへの変換を続行します。

a)メソッドnumbers.Max()が実行され、戻り値がクエリに使用される場合は、カスタム拡張メソッドを使用してこれをテストし、デバッグブレークを設定します。

b)ローカルで宣言された番号のリストの代わりに次のことを行った場合、これは正しいです

cts = from c in context.Contacts
                   where c.ContactID == context.Numbers.Max()
                   select c;

これは、Numbersテーブルのサブクエリに変換されます(戻り型がIQueryableであったため)。この場合、プロバイダーによってサポートされていないメソッドを使用した場合は、期待値が表示されます。再度これをテストするには、カスタム拡張メソッドを使用します。

c)次のようなSQLに変換される文字列StartsWithメソッドなどの正しい

于 2012-11-09T19:45:39.123 に答える