Join with Where 子句的查询和方法 (lambda) 语法的等效性
Equivalence of query and method (lambda) syntax of a Join with Where clause
我的简化 LINQ Join
加上两个表的 Where
如下所示:
var join = context.Foo
.Join(context.Bar,
foo => new { foo.Year, foo.Month },
bar => new { bar.Year, bar.Month },
(foo, bar) => new { foo.Name, bar.Owner, foo.Year })
.Where(anon => anon.Year == 2015).ToList();
或者我可以使用以下我希望等效的语法:
var joinQuery = from foo in context.Foo
join bar in context.Bar
on new { foo.Year, foo.Month } equals new { bar.Year, bar.Month }
where foo.Year == 2015
select new { foo.Name, bar.Owner };
var join = joinQuery.ToList();
我想到并想知道的一个区别是命令的顺序。在 lambda-syntax 连接中,我将 foo.Year
属性 添加到我的匿名 return 类型中,这样我就可以在之后进行过滤,而在其他查询中我仍然可以使用 foo
( bar
如果我愿意的话)在 where
子句中。如果我不想或不需要,我不需要将字段 foo.Year
添加到我的 return 类型。
不幸的是,我没有 ReSharper 或任何类似的东西可以将较低的语句转换为 lambda 语句以便我进行比较。
我实际上可以做的(并使上层语句在结构上与下层语句更相似)是在第一个 Where(..)
和 ToList()
之间添加以下行:
.Select(anon => new { /* the properties I want */ })
但是与第二条语句相比,这不只是添加了 "one more" 匿名类型创建,还是我弄错了?
简而言之: 与第二条语句等效的 Join
语法是什么?或者第一个加上添加的 Select
真的是等价的,也就是说,joinQuery
是否在内部产生相同的代码?
对于 简而言之: 部分问题:答案是肯定的。
这是 resharpers 结果
var joinQuery = context.Foo.Join(context.Bar, foo => new
{
foo.Year,
foo.Month
}, bar => new
{
bar.Year,
bar.Month
}, (foo, bar) => new
{
foo,
bar
}).Where(@t => @t.foo.Year == 2015).Select(@t => new
{
@t.foo.Name,
@t.bar.Owner
});
在一般情况下,您不能总是完全在查询理解语法和 lambda 语法之间转换,就像编译器所做的那样。这是由于使用了 transparent identifiers。但是您可以解决这个问题并生成 语义等价的 lambda 语句。这就是 ReSharper 所做的。
无论如何,在你的情况下,你可以添加:
.Select(anon => new { /* the properties I want */ })
这将每行实例化一个匿名类型,但不会是 "one more",所以不用担心:the表达式被转换为 SQL,因此 join
中的 new { foo.Year, foo.Month }
语句并没有真正实例化这些对象,它们只是被转换为 SQL。只有最后一个 select 将同时用于 SQL SELECT
列表,并在检索到行后用于对象水化。
But doesn't this just add "one more" anonymous type creation compared to the 2nd statement, or am I mistaken here?
正如 的回答所示:在这种情况下,当您使用理解语法时,这就是编译器正在做的事情。通过在您的单一匿名类型中包含年份,您可以稍微减少开销。
但是:
- 匿名类型非常轻量级:泛型的使用,以及在引用类型上实例化的泛型共享实现意味着代码很少(看看反编译器中的程序集)。
- 虽然创建了大量实例,但在大多数情况下,它们会在第 0 代中被清理,因为它们几乎会立即被释放。
- C# 编译器和 JIT 的优化器在这里有很多用处。很可能采取了捷径(但您需要从 运行 进程中读取 x86/x64 程序集才能看到)。
记住前两个 rules of optimisation:除非您可以显示 – 来自实际测试数据的分析器数据 – 您的性能问题集中在易于维护的清晰代码上。
另一种选择是将 where
方法移到 join
方法之前,然后将年份从匿名 class:
中删除
var join = context.Foo
.Where(foo => foo.Year == 2015)
.Join(context.Bar,
foo => new { foo.Year, foo.Month },
bar => new { bar.Year, bar.Month },
(foo, bar) => new { foo.Name, bar.Owner })
.ToList();
但总的来说,其他答案都是正确的,区别不大,编译器可以为您处理细节。
我的简化 LINQ Join
加上两个表的 Where
如下所示:
var join = context.Foo
.Join(context.Bar,
foo => new { foo.Year, foo.Month },
bar => new { bar.Year, bar.Month },
(foo, bar) => new { foo.Name, bar.Owner, foo.Year })
.Where(anon => anon.Year == 2015).ToList();
或者我可以使用以下我希望等效的语法:
var joinQuery = from foo in context.Foo
join bar in context.Bar
on new { foo.Year, foo.Month } equals new { bar.Year, bar.Month }
where foo.Year == 2015
select new { foo.Name, bar.Owner };
var join = joinQuery.ToList();
我想到并想知道的一个区别是命令的顺序。在 lambda-syntax 连接中,我将 foo.Year
属性 添加到我的匿名 return 类型中,这样我就可以在之后进行过滤,而在其他查询中我仍然可以使用 foo
( bar
如果我愿意的话)在 where
子句中。如果我不想或不需要,我不需要将字段 foo.Year
添加到我的 return 类型。
不幸的是,我没有 ReSharper 或任何类似的东西可以将较低的语句转换为 lambda 语句以便我进行比较。
我实际上可以做的(并使上层语句在结构上与下层语句更相似)是在第一个 Where(..)
和 ToList()
之间添加以下行:
.Select(anon => new { /* the properties I want */ })
但是与第二条语句相比,这不只是添加了 "one more" 匿名类型创建,还是我弄错了?
简而言之: 与第二条语句等效的 Join
语法是什么?或者第一个加上添加的 Select
真的是等价的,也就是说,joinQuery
是否在内部产生相同的代码?
对于 简而言之: 部分问题:答案是肯定的。
这是 resharpers 结果
var joinQuery = context.Foo.Join(context.Bar, foo => new
{
foo.Year,
foo.Month
}, bar => new
{
bar.Year,
bar.Month
}, (foo, bar) => new
{
foo,
bar
}).Where(@t => @t.foo.Year == 2015).Select(@t => new
{
@t.foo.Name,
@t.bar.Owner
});
在一般情况下,您不能总是完全在查询理解语法和 lambda 语法之间转换,就像编译器所做的那样。这是由于使用了 transparent identifiers。但是您可以解决这个问题并生成 语义等价的 lambda 语句。这就是 ReSharper 所做的。
无论如何,在你的情况下,你可以添加:
.Select(anon => new { /* the properties I want */ })
这将每行实例化一个匿名类型,但不会是 "one more",所以不用担心:the表达式被转换为 SQL,因此 join
中的 new { foo.Year, foo.Month }
语句并没有真正实例化这些对象,它们只是被转换为 SQL。只有最后一个 select 将同时用于 SQL SELECT
列表,并在检索到行后用于对象水化。
But doesn't this just add "one more" anonymous type creation compared to the 2nd statement, or am I mistaken here?
正如
但是:
- 匿名类型非常轻量级:泛型的使用,以及在引用类型上实例化的泛型共享实现意味着代码很少(看看反编译器中的程序集)。
- 虽然创建了大量实例,但在大多数情况下,它们会在第 0 代中被清理,因为它们几乎会立即被释放。
- C# 编译器和 JIT 的优化器在这里有很多用处。很可能采取了捷径(但您需要从 运行 进程中读取 x86/x64 程序集才能看到)。
记住前两个 rules of optimisation:除非您可以显示 – 来自实际测试数据的分析器数据 – 您的性能问题集中在易于维护的清晰代码上。
另一种选择是将 where
方法移到 join
方法之前,然后将年份从匿名 class:
var join = context.Foo
.Where(foo => foo.Year == 2015)
.Join(context.Bar,
foo => new { foo.Year, foo.Month },
bar => new { bar.Year, bar.Month },
(foo, bar) => new { foo.Name, bar.Owner })
.ToList();
但总的来说,其他答案都是正确的,区别不大,编译器可以为您处理细节。