2009-02-11 21 views
5

OK questo è un piccolo lamento ma è anche una domanda. In Linq posso fare un join come questo:Perché Linq si unisce in modo diverso

from c in dc.Customers join o in dc.Orders on c.custid equals o.custid ... 

Tutto molto bello e completamente rememberable senza dover tornare indietro e google. Tuttavia sinistra si unisce sono molto più complicato per qualche motivo:

from c in dc.Customers 
join o in dc.Orders on c.custid equals o.custid 
into temp from x in temp.DefaultIfEmpty() ... 

Quindi la mia domanda è: perché non potevano i progettisti di Linq rendere le cose semplici (più di sql simili) con qualcosa di simile:

da c in dc.Customers LEFT jOIN o nel dc.Orders su c.custid uguale o.custid ...

Acclamazioni Lee

risposta

0

Probabilmente perché le espressioni Linq sono semplicemente syntactic sugar nel compilatore, che li traduce in meth od chiama. Quindi la sintassi della query è un'astrazione che perde di un sistema orientato agli oggetti.

Poiché non si sta effettivamente scrivendo SQL, ci sono casi in cui la tecnologia sottostante si comporta in modo diverso. Aggiungere un "left join" di tipo SQL è probabilmente molto più difficile di quanto si pensi.


Alcune persone evidentemente non sanno come funzionano le espressioni Linq, quindi ecco una ulteriore spiegazione.

se prendo questa classe di test:

public class Class1 
{ 
    public List<string> list = new List<string>() { "test", "test1", "test2" }; 

    public void test_lambda() 
    { 
     var test = list.Where(l => l == "test1"); 
    } 

    public void test_linq() 
    { 
     var test = from l in list 
        where l == "test2" 
        select l; 
    } 
} 

list.Where(l => l == "test2") è redatto al stesso codice from l in list where l == "test2" select l. In entrambi i casi il compilatore genera metodo delegati anonimi:

.method public hidebysig instance void test_lambda() cil managed 
{ 
    .maxstack 8 
    L_0000: ldarg.0 
    L_0001: ldfld class [mscorlib]System.Collections.Generic.List`1<string> Class1::list 
    L_0006: ldsfld class [System.Core]System.Func`2<string, bool> Class1::CS$<>9__CachedAnonymousMethodDelegate1 
    L_000b: brtrue.s L_001e 
    L_000d: ldnull 
    L_000e: ldftn bool Class1::<test_lambda>b__0(string) 
    L_0014: newobj instance void [System.Core]System.Func`2<string, bool>::.ctor(object, native int) 
    L_0019: stsfld class [System.Core]System.Func`2<string, bool> Class1::CS$<>9__CachedAnonymousMethodDelegate1 
    L_001e: ldsfld class [System.Core]System.Func`2<string, bool> Class1::CS$<>9__CachedAnonymousMethodDelegate1 
    L_0023: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [System.Core]System.Func`2<!!0, bool>) 
    L_0028: pop 
    L_0029: ret 
} 

.method public hidebysig instance void test_linq() cil managed 
{ 
    .maxstack 8 
    L_0000: ldarg.0 
    L_0001: ldfld class [mscorlib]System.Collections.Generic.List`1<string> Class1::list 
    L_0006: ldsfld class [System.Core]System.Func`2<string, bool> Class1::CS$<>9__CachedAnonymousMethodDelegate3 
    L_000b: brtrue.s L_001e 
    L_000d: ldnull 
    L_000e: ldftn bool Class1::<test_linq>b__2(string) 
    L_0014: newobj instance void [System.Core]System.Func`2<string, bool>::.ctor(object, native int) 
    L_0019: stsfld class [System.Core]System.Func`2<string, bool> Class1::CS$<>9__CachedAnonymousMethodDelegate3 
    L_001e: ldsfld class [System.Core]System.Func`2<string, bool> Class1::CS$<>9__CachedAnonymousMethodDelegate3 
    L_0023: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<string>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [System.Core]System.Func`2<!!0, bool>) 
    L_0028: pop 
    L_0029: ret 
} 

Questo è ciò che intendo per zucchero sintattico. Le espressioni di query non aggiungono nulla di nuovo alla lingua, ma forniscono semplicemente un modo più semplice di utilizzare le funzionalità linguistiche esistenti.

+0

Sì, ho intuito che probabilmente era difficile - mi piacerebbe sapere perché è stato difficile però ... – user52110

+0

Linq non è zucchero sintattico. il compilatore non traduce nulla nelle espressioni lambda. Nessuno ha detto nulla sulla creazione di SQL nella domanda. -1 –

+0

OK, non sono sicuro che sia lambda di per sé, ma linq è zucchero sintattico, e viene tradotto in chiamate di metodo dal compilatore. –

5

perché non potevano i progettisti di Linq rendere le cose semplici (più sql come)

Avrebbero potuto. Ma la tua definizione di semplice (come un programmatore sql) non è la stessa della semplice definizione del programmatore OO. Linq (in C#) è una tecnologia di query per i programmatori OO, in primo luogo. Un esempio di questo è perché selezionare viene l'ultimo. Questo è per soddisfare le regole di ambito in C# e il supporto intellisense nell'editor.

Questi programmatori potrebbero non ottenere LEFT JOIN (e diventare davvero confusi se si dice LEFT OUTER JOIN - pensare che ci sia qualche differenza, come se uno erediti dall'altro).

Quello che capiscono è GROUP JOIN, che si comporta in modo simile.

List<int> myInts = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 
List<int> myOtherInts = new List<int>() { 1, 3, 5, 7, 9, 11, 13 }; 
// 
var query = from i in myInts 
    join j in myOtherInts on i equals j into g 
    select new {key = i, myGroup = g}; 
// 
foreach (var grouping in query) 
{ 
    Console.WriteLine("--{0}", grouping.key); 
    foreach (var x in grouping.myGroup) 
    Console.WriteLine(x); 
} 

Tutto ciò che DefaultIfEmpty roba non fa altro che scompattare il gruppo - appiattendo i risultati in riga/colonna forma - lontano da naturale forma gerarchica del programmatore OO.DefaultIfEmpty non è semanticamente necessario per ottenere i risultati.

Ecco la stessa query in forma di metodo - che il compilatore genera da quanto sopra, e che io preferisco:

var query = myInts.GroupJoin(
    myOtherInts, 
    i => i, 
    j => j, 
    (i, g) => new { key = i, myGroup = g } 
); 

Potrebbe affermare che dal punto di vista il suo esempio?

Questa query offre ai clienti, con i loro ordini, una raccolta allegata. La raccolta ordini potrebbe essere vuota. Se hai 50 clienti e 1000 ordini, avrai 50 clienti nel risultato.

from c in dc.Customers 
join o in dc.Orders on c.custid equals o.custid into someOrders 
select new CustomerWithOrders() 
    {theCustomer = c, theOrders = someOrders}; 

Questa query fornisce una riga CustomerOrder. Se un cliente ha 5 ordini, il cliente apparirà 5 volte, ogni volta abbinato a un ordine diverso. Se il cliente ha 0 ordini, il cliente apparirà una volta abbinato a un ordine nullo. Se hai 50 clienti e 1000 ordini, avrai 50-1049 righe dopo il join e il significato di un elemento nel risultato è difficile da definire.

from c in dc.Customers 
join o in dc.Orders on c.custid equals o.custid into temp 
from x in temp.DefaultIfEmpty() 
select new CustomerOrderHybrid() {theCustomer = c, theOrder = x} 

Se attuate left join, richiederebbe la forma risultato del secondo esempio. Una volta utilizzato lo group join, che è meglio, non implementerei lo left join in un solo passaggio. La modellazione gerarchica dei risultati della query è ottima.

+0

Non vedo come la risposta risponda alla domanda. Potresti affermarlo nei termini del suo esempio? – dkretz

+0

In primo luogo vorrei dire grazie per avermi dedicato del tempo per scrivere il tuo messaggio di provocazione. Domani guarderò da vicino, ma la mia risposta immediata è che c'è un chiaro punto di Sqlish nei confronti di Linq, già per dire che i programmatori OO non l'avrebbero capito, in realtà non si lavano come una ragione. – user52110

+0

A parte il mio cervello fa male ... ma penso di capire che i progettisti LINQ cercano di preservare le rappresentazioni gerarchiche piuttosto che quelle appiattite. – stusmith

Problemi correlati