2009-02-11 17 views
5

OK, to trochę jęczę, ale to także pytanie. W LINQ mogę łączyć tak:Dlaczego Linq łączy się inaczej

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

wszystko dobrze i całkowicie rememberable bez konieczności wrócić i google. Jednak lewa przyłącza są znacznie bardziej skomplikowane z jakiegoś powodu:

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

Więc moje pytanie brzmi, dlaczego nie mógł projektanci LINQ dokonać rzeczy proste (więcej jak sql) z mniej więcej tak:

z cw dc.Customers lewo dołączyć ow dc.Orders na c.custid równa o.custid ...

Cheers Lee

Odpowiedz

0

Prawdopodobnie dlatego, że są po prostu wyrażenia Linq syntactic sugar w kompilator, co przekłada je na met od połączeń. Tak więc składnia zapytania jest nieszczelną abstrakcją systemu obiektowego.

Ponieważ w rzeczywistości nie pisze się w języku SQL, istnieją przypadki, w których technologia bazowa zachowuje się inaczej. Dodanie "lewostronnego" języka SQL jest prawdopodobnie trudniejsze niż myślisz.


Niektórzy ludzie najwyraźniej nie wiedzą, jak działają wyrażenia Linq, więc oto dalsze wyjaśnienie.

Jeśli wezmę tej klasy badawczej:

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") jest kompilowany do tego samego kodu jako from l in list where l == "test2" select l. W obu przypadkach kompilator generuje anonimową metodę delegatów:

.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 
} 

To właśnie mam na myśli przez cukier syntaktyczny. Wyrażenia kwerendy nie dodają nic nowego do tego języka, zapewniają jedynie łatwiejszy sposób korzystania z istniejących funkcji językowych.

+0

Tak, domyślam się, że to było prawdopodobnie trudne - chciałbym się dowiedzieć, dlaczego było to trudne ... – user52110

+0

Linq nie jest cukrem syntaktycznym. kompilator nie tłumaczy niczego w wyrażeniach lambda. Nikt nie powiedział nic o generowaniu SQL w pytaniu. -1 –

+0

OK, nie jestem pewien, że to lambdas per se, ale linq IS jest syntaktycznym cukrem i zostaje przetłumaczony na wywołania metod przez kompilator. –

5

dlaczego nie mógł projektanci LINQ dokonać rzeczy proste (więcej sql podobnych)

Mogli. Ale twoja definicja prostego (jako programista SQL) nie jest taka sama, jak definicja prostego programisty OO. Linq (w języku C#) to technologia zapytań dla programistów OO. Przykładem tego jest , dlaczego wybór przychodzi ostatni. To jest spełnienie reguł zasięgu w obsłudze C# i intellisense w edytorze.

Ci programiści być może nie dostaną LEFT JOIN (i będą bardzo zdezorientowani, jeśli powiesz LEFT OUTER JOIN - myśląc, że jest jakaś różnica, tak jak jedna dziedziczy po drugiej).

To, co rozumieją, to GROUP JOIN, które zachowuje się w podobny sposób.

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

Wszystko to DefaultIfEmpty robi rzeczy jest rozpakować grupę - spłaszczenie wyników w wiersz/kolumna formy - z dala od Komisarza programisty naturalnej postaci heirarchical.DefaultIfEmpty nie jest semantycznie konieczne, aby uzyskać wyniki.

Oto to samo zapytanie w formie metody - które kompilator generuje z powyższego i które wolę:

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

mógłbyś stwierdzić, że pod względem jego przykładem?

To zapytanie dostarcza klientom, z ich zamówieniami, jako załączoną kolekcję. Zbieranie zamówień może być puste. Jeśli masz 50 klientów i 1000 zamówień, w rezultacie uzyskasz 50 klientów.

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}; 

To zapytanie daje wiersz CustomerOrder. Jeśli klient ma 5 zamówień, klient pojawi się 5 razy, za każdym razem dopasowując się do innego zamówienia. Jeśli klient ma 0 zamówień, klient pojawi się po dopasowaniu do zamówienia zerowego. Jeśli masz 50 klientów i 1000 zamówień, po złączeniu będziesz miał 50-1049 wierszy, a znaczenie elementu w wyniku jest trudne do zdefiniowania.

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} 

Jeśli one realizowane left join, wymagałoby to kształt wynika z drugiego przykładu. Kiedyś użyłem group join, co jest lepsze, nie wprowadziłbym też ani jednego kroku left join. Hierarchiczne kształtowanie wyników zapytań jest świetne.

+0

Nie widzę, jak odpowiedź na to pytanie odpowiada. Czy możesz podać to w jego przykładzie? – dkretz

+0

Najpierw chciałbym podziękować za poświęcony czas, aby napisać swoją myślącą pocztę. Będę się bliżej przyjrzeć jutro - moja bezpośrednia reakcja mówi jednak, że istnieje wyraźny styl słowny dla Linq, że tak powiem, że programiści OO tego nie zrozumieją, tak naprawdę nie umywają się jako powód. – user52110

+0

Mój mózg boli ... ale myślę, że rozumiem, że projektanci LINQ starają się zachować reprezentacje heirachów zamiast tych spłaszczonych. – stusmith

Powiązane problemy