知乎周源微信_每周源代码52-您一直在使用LINQ,我不知道您的意思是什么。

本文探讨了开发人员在使用LINQ to SQL时遇到的查询怪异现象,涉及抽象级别和SQL执行差异。作者强调了阅读源代码的重要性,并揭示了为何一个看似相同的查询在LINQ中行为不同,关键在于是否正确使用IQueryable和Expression。
摘要由CSDN通过智能技术生成
知乎周源微信

知乎周源微信

Remember good developers don't just write source code, they also READ it. You don't just become a great poet by writing lots of poems. Read and absorb as well. Do check out the Source Code category of my blog here, there is (as of today) 15 pages of posts on Source Code you can check out.

记住,优秀的开发人员不仅要编写源代码,还要阅读源代码。 通过写很多诗,您不只是成为一位伟大的诗人。 阅读并吸收。 请在此处检查我博客的“源代码”类别, (截止到今天)您可以查看15页源代码中的帖子。

Recently my friend Jonathan Carter (OData Dude, my name for him) was working with a partner on some really weird stuff that was happening with a LINQ to SQL query. Remember that every abstraction sometimes leaks and that the whole port of an abstraction is "raise the level" so you don't have to worry about something.

最近,我的朋友乔纳森·卡特(Jonathan Carter)(我以他的名字叫OData Dude)正在与一个合作伙伴一起处理LINQ to SQL查询中发生的一些非常奇怪的事情。 请记住,每个抽象有时都会泄漏,并且抽象的整个端口都是“提高级别”,因此您不必担心任何事情。

Plumbing is great because it abstracts away water delivery. For all I know, there's a dude with a bucket who runs to my house when I turn on the tap. Doesn't matter to me, as long as I get water. However, sometimes something goes wrong with that dude, and I don't understand what's up with my water. This happened to JC and this partner.

水管之所以很棒,是因为它抽象了水的输送。 据我所知,当我打开水龙头时,有一个带桶的家伙跑到我家。 只要我喝水,对我来说都没关系。 但是,有时那个家伙出现了问题,而且我不明白我的水是怎么回事。 这发生在JC和这个合作伙伴身上。

In this example, we're using the AdventureWorks Sample Database to make this point. Here's some sample code the partner sent us to reproduce the weirdness.

在此示例中,我们使用AdventureWorks示例数据库来说明这一点。 这是合作伙伴发送给我们的一些示例代码,以重现怪异之处。

protected virtual Customer GetByPrimaryKey(Func<customer, bool> keySelection)
{
AdventureWorksDataContext context = new AdventureWorksDataContext();

return (from r in context.Customers select r).SingleOrDefault(keySelection);
}

[TestMethod]
public void CustomerQuery_Test_01()
{
Customer customer = GetByPrimaryKey(c => c.CustomerID == 2);
}

[TestMethod]
public void CustomerQuery_Test_02()
{
AdventureWorksDataContext context = new AdventureWorksDataContext();
Customer customer = (from r in context.Customers select r).SingleOrDefault(c => c.CustomerID == 2);
}

CustomerQuery_Test_01 calls the GetByPrimaryKey method. That method takes a Func as a parameter. He's actually passing in a lamdba expression into the GetByPrimaryKey function. That makes the method reusable and is the beginning of some nice helper functions for his DAL (Data Access Layer). He's split up the query into two places. Seems reasonable, right?

CustomerQuery_Test_01调用GetByPrimaryKey方法。 该方法需要一个Func 作为参数。 他实际上是将lamdba表达式传递给GetByPrimaryKey函数。 这使得该方法可重用,并且是其DAL(数据访问层)一些不错的辅助功能的开始。 他将查询分为两个地方。 看起来合理吧?

Well, if you run this in Visual Studio - and in this example, I'll use the Intellitrace feature to see the actual SQL that was executed, although you can also use SQL Profiler - we see:

好吧,如果您在Visual Studio中运行此代码-在本示例中,尽管您也可以使用SQL事件探查器,但我将使用Intellitrace功能查看实际执行SQL-我们看到:

Wrong SQL in the Watch Window

Here's the query in text:

这是文本查询:

SELECT [t0].[CustomerID], [t0].[NameStyle], [t0].[Title], 
[t0].[FirstName], [t0].[MiddleName], [t0].[LastName],
[t0].[Suffix], [t0].[CompanyName], [t0].[SalesPerson],
[t0].[EmailAddress], [t0].[Phone], [t0].[PasswordHash],
[t0].[PasswordSalt], [t0].[rowguid], [t0].[ModifiedDate]
FROM [SalesLT].[Customer] AS [t0]

Um, where's the WHERE clause? Will LINQ to SQL kill my pets and cause me to lose my job? Does Microsoft suck? Let's take a look at the second query, called in CustomerQuery_Test_02():

嗯,WHERE子句在哪里? LINQ to SQL会杀死我的宠物并使我失业吗? 微软会吸吗? 让我们看一下第二个查询,在CustomerQuery_Test_02()中调用:

SELECT [t0].[CustomerID], [t0].[NameStyle], [t0].[Title], 
[t0].[FirstName], [t0].[MiddleName], [t0].[LastName],
[t0].[Suffix], [t0].[CompanyName], [t0].[SalesPerson],
[t0].[EmailAddress], [t0].[Phone], [t0].[PasswordHash],
[t0].[PasswordSalt], [t0].[rowguid], [t0].[ModifiedDate]
FROM [SalesLT].[Customer] AS [t0]
WHERE [t0].[CustomerID] = @p0

OK, there it is, but why does the second LINQ query cause a WHERE clause to be emitted but the first doesn't? They look like basically the same code path, just one is broken up.

好的,是的,但是为什么第二个LINQ查询导致发出WHERE子句,而第一个却没有? 它们看起来基本上是相同的代码路径,只是其中一个被分解了。

The first query is clearly returning ALL rows to the caller, which then has to apply the LINQ operators to do the WHERE in memory, on the caller. The second query is using the SQL Server (as it should) to do the filter, then returns WAY less data.

显然,第一个查询是将所有行返回给调用方,然后调用方必须应用LINQ运算符在内存中进行WHERE操作。 第二个查询使用SQL Server(应该这样做)进行过滤,然后返回WAY较少的数据。

Here's the deal. Remember that LINQ cares about two things, IEnumerable stuff and IQueryable. The first lets you foreach over a collection, and the other includes all sorts of fun stuff that lets you query that stuff. Folks build on top of those with LINQ to SQL, LINQ to XML, LINQ to YoMomma, etc.

这是交易。 请记住,LINQ在乎两件事,即IEnumerable和IQueryable。 第一个让您遍历一个集合,另一个让您查询各种有趣的东西。 人们在使用LINQ to SQL,LINQ to XML,LINQ to YoMomma等的人群之上构建。

When you are working with something that is IQueryable; that is, the source is IQueryable, you need to make sure you are actually usually the operators for an IQueruable, otherwise you might fall back onto an undesirable result, as in this database case with IEnumerable. You don't want to return more data from the database to a caller than is absolutely necessary.

当您使用可查询的东西时; 也就是说,源是IQueryable,您需要确保您实际上通常是IQueruable的运算符,否则,您可能会陷入不希望的结果,例如在IEnumerable的数据库情况下。 您不希望从数据库返回到调用方的数据超出绝对必要。

From JC, with emphasis mine:

来自JC,重点是我的

The IQueryable version of SingleOrDefault, that takes a lambda, actually takes an Expression > , whereas the IEnumerable version, takes a Func . Hence, in the below code, the call to SingleOrDefault, is treating the query as if it was LINQ To Objects, which executes the query via L2S, then performs the SingleOrDefault on the in memory collection. If they changed the signature of GetByPrimaryKey to take an Expression >, it would work as expected.

SingleOrDefault的IQueryable版本需要一个lambda,实际上需要一个Expression > ,而IEnumerable版本采用Func 。 因此,在下面的代码中,对SingleOrDefault的调用将查询视为LINQ To Objects,它通过L2S执行查询,然后对内存中的集合执行SingleOrDefault。 如果他们将GetByPrimaryKey的签名更改为采用表达式 >, 它会按预期工作。

What's a Func and what's an Expression? A Func<> (pronounced "Funk") represents a generic delegate. Like:

什么是Func,什么是表达? Func <>(发音为“ Funk”)表示泛型委托。 喜欢:

Func<int,int,double> divide=(x,y)=>(double)x/(double)y;
Console.WriteLine(divide(2,3));

And an Expression<> is a function definition that can be compiled and invoked at runtime. Example"

Expression <>是可以在运行时进行编译和调用的函数定义。 例”

Expression<Func<int,int,double>> divideBody=(x,y)=>(double)x/(double)y;
Func<int,int,double> divide2=divideBody.Compile();
write(divide2(2,3));

So, the partner doesn't want a Func (a Func that takes a customer and returns a bool, they want a compliable Expression with a Func that takes a Customer and returns a bool. I'll have to add "using System.Linq.Expressions;" as well.

因此,合作伙伴不希望使用Func (一个接受客户并返回布尔值的Func,他们想要一个与接受客户并返回布尔值的Func兼容的表达式。我还必须添加“ using System.Linq.Expressions;”。

protected virtual Customer GetByPrimaryKey(Expression<Func<customer,bool>> keySelection)
{
AdventureWorksDataContext context = new AdventureWorksDataContext();

return (from r in context.Customers select r).SingleOrDefault(keySelection);

}

[TestMethod]
public void CustomerQuery_Test_01()
{
Customer customer = GetByPrimaryKey(c => c.CustomerID == 2);
}

[TestMethod]
public void CustomerQuery_Test_02()
{
AdventureWorksDataContext context = new AdventureWorksDataContext();
Customer customer = (from r in context.Customers select r).SingleOrDefault(c => c.CustomerID == 2);
}

Just changed that one line, so that GetByPrimaryKey takes a Expression > and I get the SQL I expected:

只需更改这一行,以使GetByPrimaryKey接受一个Expression >并且我得到了我期望SQL:

Corrected SQL in the Watch Window

Someone famous once said, "My code has no bugs, it runs exactly as I wrote it."

一位著名的人曾经说过:“我的代码没有错误,它的运行与我编写的完全相同。”

Layers of Abstraction are tricky, and you should always assert your assumptions and always look at the SQL that gets generated/created/executed by your DAL before you put something into production. Trust no one, except the profiler.

抽象层很棘手,在将某些东西投入生产之前,您应该始终断言自己的假设,并始终查看由DAL生成/创建/执行SQL。 除了分析器外,不信任任何人。

翻译自: https://www.hanselman.com/blog/the-weekly-source-code-52-you-keep-using-that-linq-i-dunna-think-it-means-what-you-think-it-means

知乎周源微信

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值