为什么LINQ执行联接的方式与SQL不同?

kqlmhetl  于 2022-12-06  发布在  其他
关注(0)|答案(2)|浏览(141)

在LINQ中,我可以执行如下联接:

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

但是,左连接要复杂得多:

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

为什么LINQ的设计者不能用这样的东西使事情变得简单(更像SQL)呢?

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

0lvr5msh1#

why couldn't the designers of Linq make things simple (more sql like)
They could have. But your definition of simple (as a sql programmer) is not the same as the OO programmer's definition of simple. Linq (in C#) is a query technology for OO programmers, first. An example of this is why select comes last. That's to fulfill scope rules in C# and intellisense support in the editor.
These programmers maybe don't get LEFT JOIN (and get really confused if you say LEFT OUTER JOIN - thinking there's some difference, like one inherits from the other).
What they do understand is GROUP JOIN , which behaves in a similar manner.

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

All that DefaultIfEmpty stuff does is unpack the group - flattening the results into row/column form - away from the OO programmer's natural heirarchical form. DefaultIfEmpty is not semantically necessary to get to the results.
Here's the same query in method form - which the compiler generates from the above and which I prefer:

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

Could you state that in terms of his example?
This query gives you customers, with their orders as an attached collection. The orders collection might be empty. If you have 50 customers and 1000 orders, you will have 50 customers in the result.

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

This query gives you a CustomerOrder row. If a customer has 5 orders, the customer will appear 5 times, each time matched to a different order. If the customer has 0 orders, the customer will appear once matched to a null order. If you have 50 customers and 1000 orders, you will have 50-1049 rows after the join and the meaning of an element in the result is hard to define.

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}

If they implemented left join , it would require the result shape of the second example. Once I used the group join , which is better, I wouldn't implement the left join in one step either. Hierarchical shaping of query results is great.

5cg8jx4n

5cg8jx4n2#

可能是因为Linq表达式在编译器中只是syntactic sugar,编译器将它们转换为方法调用,所以查询语法是面向对象系统的一个有漏洞的抽象。
因为你实际上并不是在写SQL,所以一定会有底层技术表现不同的情况。添加一个类似SQL的“左连接”可能比你想象的要难得多。
有些人显然不知道Linq表达式是如何工作的,所以这里有一个进一步的解释。
如果我参加这个测试课程:

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")会编译成与from l in list where l == "test2" select l相同的程式码。在这两种情况下,编译器都会产生匿名方法委派:

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

这就是我所说的语法糖的意思。查询表达式并没有给语言添加任何新的东西,它们只是提供了一种使用现有语言特性的更简单的方法。

相关问题