2012-02-08 10 views
5

Ho utilizzato LINQ to SQL & per entità per un po 'e sono complessivamente molto soddisfatto di loro. Tuttavia, conosco i loro limiti e uno in particolare sta diventando un grosso problema per me. Quando si esegue una query complessa nidificato sotto forma diConsiglia un provider LINQ adatto (server SQL, query complesse)

MyContext.SomeTable 
.Select(item=>new{ 
    item.SomeProperty1, 
    item.SomeProperty2, 
    item.NavigationProperty1 
     .Select(nav1=> new {// retrieve some properties}), // This triggers a single query as long as don't have more than one subquery 
    item.NavigationProperty2 
     .Select(nav2=> new {// retrieve some properties}) // This triggers one query PER ROW in the original query 
}); 

I fornitori Ho testato sono LINQ to SQL/LINQ to Entities (e, peggio ancora, Devart LinqConnect che le tariffe peggio e genera 1 per riga sulla prima proprietà di navigazione)

quello che ottengo ora che è generato (pseudocodice):

select t1.a,t1.b,t2.c,t2.d from mytable as t1 
join navproperty1table as t2 

e 1 milioni (se non v'è di 1 milione risultati nel primo set) di query come questo: .210 (Xchanging su richiesta X all'elemento successivo restituito dalla prima query)

quello che voglio:

select t1.a,t1.b,t2.c,t2.d,t3.e,t3.f from mytable as t1 
join navproperty1table as t2 
join navproperty2table as t3 

Ora, naturalmente, se ci fossero 3 righe della tabella originale non sarebbe un problema, ma ho 10 o migliaia di milioni di righe nelle mie tabelle "e" ho bisogno di una query molto più complessa in una singola selezione (voglio ottenere un grafico complesso in una volta). Pensa a 20 + tavoli con 3-6 livelli di nidificazione accedendo ad altri 2-5 tavoli ciascuno.

Il mio server SQL può perfettamente far fronte, non mi interessa neanche la larghezza di banda, è su un'istanza collegata da una connessione gigabit, non riesco a ottenere quei dati in modo differito, in realtà "uso" tutti immediatamente, quindi non è solo pigrizia. In questo momento per motivi di prestazioni ho dovuto dividere la query in molte piccole query e unirle manualmente sul LINQ alla dimensione dell'oggetto, che fornisce un codice veramente sgradevole per chiunque lo mantenga, ma era l'unica soluzione effettiva che avevo, quindi includendo tutto il piccole query e l'unione finale, sono a oltre 600 righe di codice non ordinate in un unico metodo che è totalmente non-mantenibile.

Esistono attualmente "qualsiasi" produzione di provider LINQ pronte prima che io vada a valutare tutti quelli che funzionano in questo modo o sono meglio codificare e commercializzare il mio? (Sono molto sorpreso che in realtà non funzionino tutti in questo modo, non riesco a vedere una singola istanza dove starai meglio con il caso foreach e quelli che ho provato a dichiarare di sbarazzarsi di n +1 con loadwith, non liberartene dato che continuano a fare n + 1 query, ma lo fanno in una sola chiamata, 1 andata e ritorno & n + 1 query non è soddisfacente quando 1 è 10 000 e 10 000 000 e poi 10 000 000 000)

  • (nota che sto speculando su ciò che fa scattare esattamente questo, ma non è il problema, non importa quello che fa scattare questa "esattamente" sono sicuro di colpire in il mio contesto attuale)

PS: si noti che sto eseguendo .NET 4.0 full profi su un server Windows 2008 o versioni successive e su server SQL 2008 o versioni successive, un provider che non supporta nient'altro andrebbe bene, ho zero requisiti per migrazione, portabilità, versioni .net inferiori, supporto server SQL inferiore ecc. a versioni ancora più recenti è un'opzione, se necessario.Inoltre non ho prerequisiti per la modellazione o le funzionalità avanzate, il DB è già lì, voglio solo interrogare le tabelle, quindi qualcosa che non supporta la modellazione/viste/DML/stored procedure/funzioni va bene, il mio unico requisito è la generazione SQL sensibile sulle query complesse e grafici di oggetti

EDIT: per chiarimenti ecco un esempio reale del problema su un DB tutti possono ottenere, AdventureWorks

Interrogazione dipendenti per ogni contatto

Contacts 
.Select(cont=>new 
{ 
    cont.EmailAddress, 
    cont.EmailPromotion, 
    Employees = cont.Employees 
     .Select(emp=>new 
     { 
      emp.Gender, 
      emp.HireDate 
     }).ToList() 
}).ToList() 

Genera

SELECT [t0].[EmailAddress], [t0].[EmailPromotion], [t1].[Gender], [t1].[HireDate], (
SELECT COUNT(*) 
FROM [HumanResources].[Employee] AS [t2] 
WHERE [t2].[ContactID] = [t0].[ContactID] 
) AS [value] 

FROM [Person]. [Contattare] AS [T0] LEFT OUTER JOIN [HumanResources]. [Employee] AS [t1] ON [t1]. [ContactID] = [t0]. [ContactID] ORDINE BY [t0]. [ContactID], [t1]. [EmployeeID]

Ora l'interrogazione solo fornitori per ogni contatto Contatti .Selezionare (cont => nuova { cont.EmailAddress, cont.EmailPromotion, Fornitori = cont.VendorContacts.Select (vend => new { vend.ContactTypeID, vend.ModifiedDate..}) ToList() }) ToList()

ancora ok:.

SELECT [t0].[EmailAddress], [t0].[EmailPromotion], [t1].[ContactTypeID], [t1].[ModifiedDate], (
SELECT COUNT(*) 
FROM [Purchasing].[VendorContact] AS [t2] 
WHERE [t2].[ContactID] = [t0].[ContactID] 
) AS [value] 

FROM [persona] [Contattare] AS [T0] LEFT OUTER JOIN [Acquisti] [VendorContact. ] AS [t1] ON [t1]. [ContactID] = [t0]. [ContactID] ORDER BY [T0]. [ContactID], [t1]. [VendorID]

Ora interrogazione entrambi contemporaneamente (trigger X row query)

Contacts 
.Select(cont=>new 
{ 
    cont.EmailAddress, 
    cont.EmailPromotion, 
    Employees = cont.Employees 
     .Select(emp=>new 
     { 
      emp.Gender, 
      emp.HireDate 
     }).ToList(), 
    Vendors = cont.VendorContacts.Select(vend=>new 
    { 
     vend.ContactTypeID, 
     vend.ModifiedDate 
    }).ToList() 
}).ToList() 

Genera il brutto e lento (non incollare tutto per ovvie ragioni, ma si ottiene il punto):

SELECT [t0].[EmailAddress], [t0].[EmailPromotion], [t1].[Gender], [t1].[HireDate], (
SELECT COUNT(*) 
FROM [HumanResources].[Employee] AS [t2] 
WHERE [t2].[ContactID] = [t0].[ContactID] 
) AS [value], [t0].[ContactID] 
FROM [Person].[Contact] AS [t0] 
LEFT OUTER JOIN [HumanResources].[Employee] AS [t1] ON [t1].[ContactID] = [t0].[ContactID] 
ORDER BY [t0].[ContactID], [t1].[EmployeeID] 
GO 

-- Region Parameters 
DECLARE @x1 Int = 1 
-- EndRegion 
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate] 
FROM [Purchasing].[VendorContact] AS [t0] 
WHERE [t0].[ContactID] = @x1 
GO 

-- Region Parameters 
DECLARE @x1 Int = 2 
-- EndRegion 
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate] 
FROM [Purchasing].[VendorContact] AS [t0] 
WHERE [t0].[ContactID] = @x1 
GO 

-- Region Parameters 
DECLARE @x1 Int = 3 
-- EndRegion 
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate] 
FROM [Purchasing].[VendorContact] AS [t0] 
WHERE [t0].[ContactID] = @x1 
GO 

-- Region Parameters 
DECLARE @x1 Int = 4 
-- EndRegion 
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate] 
FROM [Purchasing].[VendorContact] AS [t0] 
WHERE [t0].[ContactID] = @x1 
GO 

-- Region Parameters 
DECLARE @x1 Int = 5 
-- EndRegion 
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate] 
FROM [Purchasing].[VendorContact] AS [t0] 
WHERE [t0].[ContactID] = @x1 
GO 

-- Region Parameters 
DECLARE @x1 Int = 6 
-- EndRegion 
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate] 
FROM [Purchasing].[VendorContact] AS [t0] 
WHERE [t0].[ContactID] = @x1 
GO 

-- Region Parameters 
DECLARE @x1 Int = 7 
-- EndRegion 
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate] 
FROM [Purchasing].[VendorContact] AS [t0] 
WHERE [t0].[ContactID] = @x1 
GO 

-- Region Parameters 
DECLARE @x1 Int = 8 
-- EndRegion 
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate] 
FROM [Purchasing].[VendorContact] AS [t0] 
WHERE [t0].[ContactID] = @x1 
GO 

-- Region Parameters 
DECLARE @x1 Int = 9 
-- EndRegion 
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate] 
FROM [Purchasing].[VendorContact] AS [t0] 
WHERE [t0].[ContactID] = @x1 
GO 

-- Region Parameters 
DECLARE @x1 Int = 10 
-- EndRegion 
SELECT [t0].[ContactTypeID], [t0].[ModifiedDate] 
FROM [Purchasing].[VendorContact] AS [t0] 
WHERE [t0].[ContactID] = @x1 
GO 

quello che mi aspetto/vorrebbero vedere generato:

SELECT [t0].[EmailAddress], [t0].[EmailPromotion], [t1].[Gender], [t1].[HireDate], [t2].[ContactTypeID], [t2].[ModifiedDate] ,[t0].[ContactID] 
FROM [Person].[Contact] AS [t0] 
LEFT OUTER JOIN [HumanResources].[Employee] AS [t1] ON [t1].[ContactID] = [t0].[ContactID] 
LEFT OUTER JOIN [Purchasing].[VendorContact] AS [t2] ON [t2].[ContactID] = [t0].[ContactID] 
GO 
+0

In realtà attiva una query per riga oppure genera una sottoquery correlata che SQL Server potrebbe ottimizzare per un piano di esecuzione univoco? –

+0

Genera una sottoquery (che va bene) quando c'è al massimo 1 proprietà nav, dopo di che genera una query effettiva "per" riga, che è folle, come in select bla da t1 dove id = qualche numero effettivo non una condizione , questa riga si è ripetuta 1 milione di volte –

+0

Immagino che tu stia cercando una soluzione che produca una query con 1 join per il primo 'Select()' su una proprietà di navigazione, seguita da 1 query per successiva 'Select()' (con una clausola WHERE sull'ID genitore)? Immagino che tu non voglia una sola query con un prodotto cartesiano inevitabile. –

risposta

0

ho trovato un fornitore che sembra gestire il mio problema principale (la generazione sana di SQL vs generare milioni di dichiarazioni a sottoquery), non so se è è una buona misura ancora in quanto dipende dalle loro risposte.

http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=20658&StartAtMessage=0&#116494

altri provider che dovrei sapere di? Se riuscissi a perdere questo fino ad ora potrebbero essercene altri e sarei lieto di confrontarli. Quello che ho ora è

fallisce totalmente la mia richiesta per il n ° 1 di query per numero di fila: - LINQ to SQL - LINQ to Entities - Devart LinqConnect

sembra funzionare - LLBLGen

testato/bisogno di un feedback - Telerik OpenAccess - NHibernate - Mindscape Lightspeed

tutti gli altri che dovrei sapere off?

+0

Accettare la mia risposta come nessuno come suggerito un fornitore funzionante da quando ho trovato questo. –

1

Una soluzione sarebbe per creare un view

dalla tua definizione

select t1.a,t1.b,t2.c,t2.d,t3.e,t3.f from mytable as t1 join navproperty1table as t2 join navproperty2table as t3 

e utilizzare linq-2-sql per richiedere tale visualizzazione.

Non sono sicuro se ho ben capito la query completamente, ma uou potrebbe solo fare

from x in MyContext.Sometable 
Select new { x.a, x.b, x.t2.c, x.t2.d, x.t3.f } 

e così via .. non posso provarlo in questo momento, ma sono abbastanza sicuro yhis creeranno select (e solo uno) tu vuoi.

+0

Creare una vista non è davvero un'opzione per 2 ragioni: 1) Voglio evitare qualsiasi cosa abbia a che fare con SQL, l'obiettivo di usare LINQ era di eliminare tutto SQL e manipolare i grafici degli oggetti e 2) restituire oggetti piatti, mentre ho bisogno di un grafico, costruendo il grafico per mano sarebbe ancora più difficile da mantenere rispetto al mio codice attuale (tieni presente che stiamo parlando di molti livelli di nidificazione con molte tabelle ciascuno, diavolo anche se tutto ha funzionato e potrei digitare la query LINQ come dovrebbe essere naturalmente la query sarebbe facilmente lunghe 300 righe) –

+0

Dividi in 2 commenti a causa delle restrizioni sulle dimensioni, inoltre non sto cercando una soluzione alternativa, la mia soluzione attuale si comporta bene, sto solo cercando di rendere il codice mantenibile facendo in modo che rifletta il grafico dell'oggetto reale (invece di tonnellate di dati flat successivamente aggiunti), e l'esempio di query che hai fornito non avrebbe funzionato, come puoi vedere dai selects sulle proprietà di navigazione, T2 e T3 sarebbero insiemi quindi non c'è modo di fare T2.c perché ci sono molti elementi con " c "su T2, che ci riporta ai selettivi nidificati e alla generazione di codici errati –

0

Penso che il più vicino possibile è quello di NHibernate Fetch (no linq).

Con dati profondamente annidati (ad es.ThenFetchMany), non sarei sorpreso se avessi raggiunto rapidamente anche i limiti di NHibernate. Le query complesse sono sempre molto difficili con gli strumenti O/RM. NHibernate mi ha sempre impressionato quando si tratta di SQL generato (da linq-to-nhibernate non ancora!). Ma anche con scenari meno complessi a volte era un lavoro infernale per prevenire il problema 1 + N.

Forse con lo HQL di NHibernate puoi ottenere ciò che desideri.

Con linq, penso che il meglio che si possa fare è recuperare il grafico dell'oggetto richiesto nel minor numero possibile di query.

+0

Accetterà questo come risposta dopo un po 'a meno che qualcuno non suggerisca un provider linq fuori dalla scatola che fa ciò di cui ho bisogno. –

0

Si potrebbe anche fare qualcosa di simile:

var venderContacts= VendorContacts.ToLookup (u =>u.ContactID); 
var contracts=Contacts 
    .Select(cont=>new 
    { 
     cont.EmailAddress, 
     cont.EmailPromotion, 
     Employees = cont.Employees 
      .Select(emp=>new 
      { 
       emp.Gender, 
       emp.HireDate 
      }).ToList(), 
     Vendors = venderContacts[cont.ContanctID] 
    }).ToList(); 
+0

Sto già facendo qualcosa di simile, come ho detto non ho alcun problema a lavorare in questo momento, voglio solo evitare questo casino e avere un provider linq (cioè cambiare l'API, "non" cambiare il mio codice) in modo che il codice possa essere pulito. Un provider decente dovrebbe essere in grado di mappare un grafico senza soluzioni alternative –