2009-02-11 13 views
5

OK, esto es un pequeño quejido pero también es una pregunta. En LINQ que pueda hacer una unión como esta:¿Por qué Linq se une de manera diferente

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

Todo muy bien y completamente rememberable sin tener que volver atrás y google. Sin embargo se une a izquierda son mucho más complicados, por alguna razón:

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

Así que mi pregunta es ¿por qué no podrían los diseñadores de LINQ hacer las cosas simples (más sql similares) con algo como esto:

de C en dc.Customers dejaron unirse o en dc.Orders en c.custid es igual o.custid ...

Saludos Lee

Respuesta

0

Probablemente porque las expresiones LINQ son simplemente syntactic sugar en el compilador, que las traduce a la metanfetamina od llamadas. Por lo tanto, la sintaxis de consulta es una abstracción con fugas de un sistema orientado a objetos.

Dado que en realidad no está escribiendo SQL, existen casos en los que la tecnología subyacente se comporta de manera diferente. Agregar una 'combinación izquierda' similar a SQL es probablemente mucho más difícil de lo que piensas.


Algunas personas evidentemente no saben cómo funcionan las expresiones de Linq, así que aquí hay una explicación más.

Si tomo esta clase de prueba:

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") se compila con el mismo código que from l in list where l == "test2" select l. En ambos casos, el compilador genera delegados método anónimo:

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

Esto es lo que quiero decir con azúcar sintáctico. Las expresiones de consulta no agregan nada nuevo al idioma, solo proporcionan una manera más fácil de usar las funciones de idioma existentes.

+0

Sí, supuse que probablemente era difícil, pero me gustaría saber por qué fue difícil ... – user52110

+0

Linq no es azúcar sintáctica. el compilador no traduce nada EN expresiones lambda. Nadie dijo nada sobre la generación de SQL en la pregunta. -1 –

+0

OK, no estoy seguro de que sea lambdas per se, pero linq ES azúcar sintáctico, y el compilador lo traduce en llamadas a métodos. –

5

por qué no podrían los diseñadores de LINQ hacer las cosas simples (más sql como)

que podrían tener. Pero su definición de programador simple (como un programador sql) no es lo mismo que la definición simple del programador OO. Linq (en C#) es una tecnología de consulta para programadores OO, primero. Un ejemplo de esto es por qué select viene el último. Eso es para cumplir con las reglas de alcance en C# y el soporte intellisense en el editor.

Estos programadores quizás no obtengan LEFT JOIN (y se confunden realmente si dice LEFT OUTER JOIN - pensando que hay alguna diferencia, como una hereda de la otra).

Lo que sí entienden es GROUP JOIN, que se comporta de manera similar.

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

Todo lo que hace es DefaultIfEmpty cosas desempaquetar el grupo - aplanando los resultados en fila/columna de forma - lejos de forma jerárquica natural del programador OO.DefaultIfEmpty no es semánticamente necesario para llegar a los resultados.

Ésta es la misma consulta en forma de método - la que el compilador genera a partir de lo anterior y que yo prefiero:

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

Podría decir que en términos de su ejemplo?

Esta consulta le brinda a los clientes, con sus pedidos como una colección adjunta. La colección de pedidos puede estar vacía. Si tiene 50 clientes y 1000 pedidos, tendrá 50 clientes en el resultado.

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

Esta consulta le proporciona una fila CustomerOrder. Si un cliente tiene 5 pedidos, el cliente aparecerá 5 veces, cada vez que coincida con un pedido diferente. Si el cliente tiene 0 pedidos, el cliente aparecerá una vez que coincida con un pedido nulo. Si tiene 50 clientes y 1000 pedidos, tendrá 50-1049 filas después de la unión y el significado de un elemento en el resultado es difícil de definir.

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} 

Si se implementan left join, se requeriría la forma resultado del segundo ejemplo. Una vez que utilicé el group join, que es mejor, tampoco implementaría el left join en un solo paso. La configuración jerárquica de los resultados de las consultas es excelente.

+0

No veo cómo responde tu respuesta a la pregunta. ¿Podrías decir eso en términos de su ejemplo? – dkretz

+0

Me gustaría darle las gracias por tomarse el tiempo de escribir su sugerencia. Lo miraré más de cerca mañana; sin embargo, mi respuesta inmediata es decir que hay un claro sesgo de Sqlish en Linq, ya que decir que los programadores de OO no lo conseguirían, no funciona como una razón. – user52110

+0

Ouch me duele el cerebro ... pero creo que entiendo acerca de los diseñadores de LINQ que intentan preservar las representaciones heiránicas en lugar de las aplastadas. – stusmith

Cuestiones relacionadas