2009-05-13 8 views
7

ho una query LINQ to SQL:LINQ to SQL Prendi w/o Ignora cause più istruzioni SQL

from at in Context.Transaction 
select new { 
    at.Amount, 
    at.PostingDate, 
    Details = 
     from tb in at.TransactionDetail 
     select new { 
      Amount = tb.Amount, 
      Description = tb.Desc 
     } 
} 

Ciò si traduce in corso di esecuzione un'istruzione SQL. Va tutto bene.

Tuttavia, se provo a restituire tipi noti da questa query, anche se hanno la stessa struttura dei tipi anonimi, ottengo un'istruzione SQL eseguita per il livello superiore e quindi un'istruzione SQL aggiuntiva per ciascun "figlio" impostato.

C'è un modo per ottenere LINQ su SQL per emettere un'istruzione SQL e utilizzare tipi noti?

EDIT: Devo avere un altro problema. Quando ho inserito una versione molto semplicistica (ma ancora alla perfezione) della mia query in LINQPad e ho utilizzato tipi conosciuti appena creati con solo 2 o 3 membri, ho ricevuto una dichiarazione SQL. Pubblicherò e aggiornerò quando ne saprò di più.

MODIFICA 2: Ciò sembra essere dovuto a un errore in Take. Vedi la mia risposta qui sotto per i dettagli.

risposta

11

Primo: alcuni ragionamenti per il bug di Take.

Se si usa il solo per il, il traduttore di query utilizza solo la parte superiore. Top10 non darà la risposta giusta se la cardinalità viene interrotta partecipando a una raccolta secondaria. Quindi il traduttore di query non partecipa alla raccolta secondaria (invece esegue la query per i figli).

Se Skip e Take, poi il traduttore interrogazione calci con una certa logica RowNumber negli righe padre ... questi rownumbers Che prenda 10 genitori, anche se questo è davvero 50 record a causa di ciascun genitore con 5 figli .

Se si Skip (0) e Take, Skip viene rimosso come non-operazione dal traduttore - è proprio come non hai mai detto Skip.

Questo sarà un duro salto concettuale da dove vi trovate (chiamando Skip e Take) a una "soluzione semplice". Quello che dobbiamo fare è forzare la traduzione a verificarsi in un punto in cui il traduttore non può rimuovere Skip (0) come non-operazione. Dobbiamo chiamare Skip e fornire il numero saltato in un secondo momento.

DataClasses1DataContext myDC = new DataClasses1DataContext(); 
    //setting up log so we can see what's going on 
myDC.Log = Console.Out; 

    //hierarchical query - not important 
var query = myDC.Options.Select(option => new{ 
    ID = option.ParentID, 
    Others = myDC.Options.Select(option2 => new{ 
    ID = option2.ParentID 
    }) 
}); 
    //request translation of the query! Important! 
var compQuery = System.Data.Linq.CompiledQuery 
    .Compile<DataClasses1DataContext, int, int, System.Collections.IEnumerable> 
    ((dc, skip, take) => query.Skip(skip).Take(take)); 

    //now run the query and specify that 0 rows are to be skipped. 
compQuery.Invoke(myDC, 0, 10); 

Questo produce la seguente query:

SELECT [t1].[ParentID], [t2].[ParentID] AS [ParentID2], (
    SELECT COUNT(*) 
    FROM [dbo].[Option] AS [t3] 
    ) AS [value] 
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[ID]) AS [ROW_NUMBER], [t0].[ParentID] 
    FROM [dbo].[Option] AS [t0] 
    ) AS [t1] 
LEFT OUTER JOIN [dbo].[Option] AS [t2] ON 1=1 
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p1 + @p2 
ORDER BY [t1].[ROW_NUMBER], [t2].[ID] 
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [0] 
-- @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [0] 
-- @p2: Input Int (Size = 0; Prec = 0; Scale = 0) [10] 
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.30729.1 

E qui è dove si vince!

WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p1 + @p2 
+0

Funziona ma ho dovuto usare IEnumerable invece di solo IEnumerable per chiamare ToList. È anche mezzo secondo più veloce nel mio caso di test. Bella risposta. – JohnOpincar

0

Non ho avuto la possibilità di provare questo, ma dato che il tipo anonimo non è parte di LINQ, piuttosto un C# costrutto Mi chiedo se è possibile utilizzare:

from at in Context.Transaction 
select new KnownType(
    at.Amount, 
    at.PostingDate, 
    Details = 
     from tb in at.TransactionDetail 
     select KnownSubType(
       Amount = tb.Amount, 
       Description = tb.Desc 
     ) 
} 

Ovviamente dettagli sarebbe bisogno di essere una collezione IEnumerable.

Potrei essere miglia su questo, ma potrebbe almeno darti una nuova linea di pensiero da perseguire che non può ferire, per favore scusa le mie deviazioni.

2

Ora ho determinato che questo è il risultato di un bug orribile. L'anonimo contro il tipo noto risultò non essere la causa. La vera causa è Take.

il seguente risultato nella dichiarazione 1 SQL:

query.Skip(1).Take(10).ToList(); 
query.ToList(); 

Tuttavia, la seguente mostra la dichiarazione di sql uno per ogni problema riga padre.

query.Skip(0).Take(10).ToList(); 
query.Take(10).ToList(); 

Qualcuno può pensare a semplici soluzioni alternative per questo?

MODIFICA: l'unica soluzione che ho trovato è quella di verificare se sono nella prima pagina (IE Skip (0)) e quindi effettuare due chiamate, una con Take (1) e l'altra con Skip (1) .Take (pageSize - 1) e aggiungiRange le liste insieme.

+0

non è il problema qui semplicemente che si richiama l'esecuzione di SQL con il primo ToList '()' e poi la seconda 'ToList()' invoca una query per ogni elemento dei risultati del primo che sono ora in memoria? –

+0

No, non è questo il problema. – JohnOpincar