Annidamento della query Linq-to-Objects nella query Linq-to-Entities-che cosa sta succedendo sotto le copertine?

2
   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); 

La query Linq-to-Entities viene prima tradotta in tree di espressione di Linq, che viene quindi convertita da Object Services in albero dei comandi . E se la query Linq-to-Entities annida la query Linq-to-Objects, anche questa query nidificata viene tradotta in un albero di espressioni .

a) Presumo che nessuno degli operatori della query Linq-to-Objects nidificata venga effettivamente eseguito, ma invece fornitore di dati per DB particolare (o forse Object Services) sa come trasformare la logica di operatori Linq-to-Objects in istruzioni SQL appropriate?

b) Il fornitore di dati sa come creare istruzioni SQL equivalenti solo per alcuni degli operatori Linq-to-Objects?

c) Analogamente, fornitore di dati sa come creare istruzioni SQL equivalenti solo per alcuni dei metodi non Linq nella libreria di classi Net Framework?

Modifica

Conosco solo alcuni Sql quindi non posso essere completamente sicuro, ma leggendo la query Sql generata per il codice precedente sembra che il fornitore di dati non abbia effettivamente eseguito il metodo numbers.Max , ma invece in qualche modo ha capito che numbers.Max dovrebbe restituire il valore massimo e quindi procedere per includere nella query Sql generata una chiamata alla funzione MAX incorporata di TSQL . Mette anche tutti i valori tenuti da numbers array in una query Sql.

 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))

In base a ciò, è possibile che il provider Linq2Entities non esegua effettivamente metodi non Linq e Linq-to-Object, ma crea invece istruzioni SQL equivalenti per alcuni di essi (e per altri genera un'eccezione)?

Grazie in anticipo

    
posta carewithl 05.11.2012 - 19:27
fonte

1 risposta

3

Presumibilmente, il fornitore di dati esamina ciascuna sottoespressione e, se può essere calcolata, esegue semplicemente una compilazione ed esegue mentre sta costruendo la query. Questo non sarebbe diverso dal dire:

where c.ContactID == (4 + 5)

Questo perché numbers nel tuo esempio è un tipo int[] .

È interessante notare che questo viene valutato solo una volta prima che venga generata la query, quindi viene calcolato solo una volta. Se hai fatto qualcosa del genere:

where c.ContactID == myObject.NextID()

... quindi valuta myObject.NextID() durante la costruzione della query, quindi la chiama solo una volta. In Linq2NHibernate ho visto che non solo lo chiama solo una volta durante la creazione della query, ma il provider di dati ha una cache di query incorporata, e non lo rivaluterà la prossima volta che eseguire la stessa query . YMMV.

Ricorda anche che è pigro valutato, quindi se non esegui mai la query, la sottoespressione non viene mai compilata ed eseguita. Inoltre, se modifichi numbers dopo l'istruzione, ma prima di eseguire foreach , la query verrà creata utilizzando il valore modificato.

Modifica

Ecco un articolo chiamato Procedura dettagliata: creazione di un provider LINQ IQueryable . Controlla la sezione su "Aggiunta di Expression Evaluator". In particolare la classe Nominator :

/// <summary> 
/// Performs bottom-up analysis to determine which nodes can possibly 
/// be part of an evaluated sub-tree. 
/// </summary> 

Fondamentalmente sta cercando l'albero delle espressioni per tutto ciò che può essere valutato, compilandolo ed eseguendolo, quindi sostituendo quella sotto-espressione con un'espressione costante.

Ecco la funzione che determina se è possibile valutare un'espressione:

private static bool CanBeEvaluatedLocally(Expression expression)
{
    return expression.NodeType != ExpressionType.Parameter;
}

Quindi, finché nessuna sottoespressione contiene un parametro dell'espressione, può essere valutata.

Modifica 2:

In base alla tua modifica, fa sembra che stia mettendo i valori dell'array in una tabella in SQL e selezionando MAX da quello. Onestamente non ho idea del motivo per cui dovrebbe preoccuparsi di farlo quando nessuno degli elementi della sottoespressione numbers.Max() sono parametri. Sicuramente so che puoi scrivere un'espressione come numbers.Contains(c.ID) e che creerà effettivamente una clausola IN come c.ID IN (@p0, @p1, @p2, @p3, @p4) dove @p0 = 1 , @p1 = 2 , ecc.

Quindi, per rispondere alla tua domanda: "è possibile che il provider Linq2Entities non esegua effettivamente metodi non Linq e Linq-to-Object, ma crea invece istruzioni SQL equivalenti per alcuni di essi"

Sì, è possibile.

    
risposta data 05.11.2012 - 19:39
fonte

Leggi altre domande sui tag