为什么说 LINQ 要胜过 SQL 已翻译 100%

oschina 投递于 2017/02/22 17:55 (共 11 段, 翻译完成于 02-24)
阅读 14966
收藏 60
3
加载中

如果你还没有沉溺于 LINQ,就会想这有啥大惊小怪的。SQL 并没有坏掉,为什么还要对它进行修补呢? 为什么我们还需要另外一种查询语言呢?

流行的说法是 LINQ 同 C#(或者 VB)集成在了一起,故而消除了编程语言和数据库之间配合上的鸿沟,同时为多个数据源的组合提供了单一的查询接口。虽然这些都是事实,但仅是故事的一部分。更重要的是:当要对数据库进行查询的时候,LINQ 在大多数情况下都比 SQL 更加有效。

同 SQL 相比, LINQ 更简单、整洁而且高级。这样子更像是拿 C# 同 C++ 做比较。真的,尽管有时候使用 C++ 仍然是最好的选择(比如使用 SQL 的场景),但在大多数场景中,使用现代整洁的语言而不必为底层细节操作就是一项大胜利。

SQL 是一门非常古老的语言—发明于 1974 年。虽然经历过了无数此扩展,但从来没有被重新设计过。这就使得它有点混乱了—不像是 VB6 或者 Visual FoxPro。你也许已经慢慢变得习惯于此因而看不到任何错漏的地方!

LeoXu
翻译于 2017/02/22 22:42
0

让我们来看一个例子。你想要编写一个简单的查询来获取客户数据,如下:

SELECT UPPER(Name)
FROM Customer
WHERE Name LIKE 'A%'
ORDER BY Name

现在假设要将结果集里的这些数据提供给一个网页,并且我们想获取第 21 到 30 行数据。所以我们需要一个子查询:

SELECT UPPER(Name) FROM
(
   SELECT *, RN = row_number()
   OVER (ORDER BY Name)
   FROM Customer
   WHERE Name LIKE 'A%'
) A
WHERE RN BETWEEN 21 AND 30
ORDER BY Name

而如果你需要支持版本(在 SQL Server 2005 之前的)更老的数据库,情况会更糟糕:

SELECT TOP 10 UPPER (c1.Name)
FROM Customer c1
WHERE
   c1.Name LIKE 'A%'
   AND c1.ID NOT IN
   (
      SELECT TOP 20 c2.ID
      FROM Customer c2
      WHERE c2.Name LIKE 'A%'
      ORDER BY c2.Name
   ) 
ORDER BY c1.Name

这样做不仅复杂而混乱,而且也违背了 DRY 原则。如下是使用 LINQ 实现相同的查询功能。显然在简单性上更胜一筹:

var query =
   from c in db.Customers
   where c.Name.StartsWith ("A")
   orderby c.Name
   select c.Name.ToUpper();

var thirdPage = query.Skip(20).Take(10);

只有当我们枚举到 thirdPage 时,查询才会实际执行。在从 LINQ 到 SQL 或者 Entity Framework 的场景中,翻译引擎会将(我们用两个步骤组合而成的)查询转换成一个 SQL 语句,这个语句是针对其所连接的数据库服务器进行了优化的。

LeoXu
翻译于 2017/02/22 22:52
0

可组合性

您可能已经注意到 LINQ 的另一个更微妙(微妙但意义重大)的好处。我们选择了组合中的两个查询步骤:

IQueryable<T> Paginate<T> (this IQueryable<T> query, int skip, int take)
{
   return query.Skip(skip).Take(take);
}

我们可以这样做:

var query = ...
var thirdPage = query.Paginate (20, 10);

更重要的是,在这里我们可以进行任意的分页查询。换言之就是通过 LINQ 你可以把查询分解成一部分,然后在你的应用程序中重用。

溪边九节
翻译于 2017/02/22 18:04
1

联合

LINQ 另一好处就是你可以不用 JOIN 就能进行关系间查询。例如,我们想要列出所有购物在 $1000 或者以上,并且居住在华盛顿的顾客。我们会假定让购买项目化(也就是经典的采购/项目采购场景)并且把(没有顾客记录的)现金销售也囊括进来。这就需要在四个表(Purchase, Customer, Address 以及 PurchaseItem)之间进行查询。使用 LINQ,这样的查询不费吹灰之力:

from p in db.Purchases
where p.Customer.Address.State == "WA" || p.Customer == null
where p.PurchaseItems.Sum (pi => pi.SaleAmount) > 1000
select p

将此与同等功能的 SQL 相比较:

SELECT p.*
FROM Purchase p
    LEFT OUTER JOIN 
        Customer c INNER JOIN Address a ON c.AddressID = a.ID
    ON p.CustomerID = c.ID	
WHERE
   (a.State = 'WA' || p.CustomerID IS NULL)
    AND p.ID in
    (
        SELECT PurchaseID FROM PurchaseItem
        GROUP BY PurchaseID HAVING SUM (SaleAmount) > 1000
    )

对此例进一步扩展,假设我们想要将结果集按价格进行逆序排列,并在最终的投影中显示销售员的姓名以及所购买项目的数量。我们可以自然不重复地表达出这些附件的查询条件:

from p in db.Purchases
where p.Customer.Address.State == "WA" || p.Customer == null
let purchaseValue = p.PurchaseItems.Sum (pi => pi.SaleAmount)
where purchaseValue > 1000
orderby purchaseValue descending
select new
{
   p.Description,
   p.Customer.SalesPerson.Name,
   PurchaseItemCount = p.PurchaseItems.Count()
}
LeoXu
翻译于 2017/02/22 23:07
0

下面是使用 SQL 实现相同的查询:

SELECT 
    p.Description,
    s.Name,
    (SELECT COUNT(*) FROM PurchaseItem pi WHERE p.ID = pi.PurchaseID) PurchaseItemCount	
FROM Purchase p
    LEFT OUTER JOIN 
        Customer c 
            INNER JOIN Address a ON c.AddressID = a.ID
            LEFT OUTER JOIN SalesPerson s ON c.SalesPersonID = s.ID
    ON p.CustomerID = c.ID	
WHERE
    (a.State = 'WA' OR p.CustomerID IS NULL)
    AND p.ID in
    (
        SELECT PurchaseID FROM PurchaseItem
        GROUP BY PurchaseID HAVING SUM (SaleAmount) > 1000
    )
ORDER BY
    (SELECT SUM (SaleAmount) FROM PurchaseItem pi WHERE p.ID = pi.PurchaseID) DESC

有意思的是可以将上述 SQL 查询转换回到 LINQ,所生成的查询每一块都会有傻瓜式重复。论坛里常会贴出这样的查询(通常是非工作的版本)——这是用 SQL 进行思考而不是以 LINQ 进行思考的结果。这就像是是将 Fortran 程序转换成 C# 6 时会抱怨 GOTO 的笨拙语法一样。

LeoXu
翻译于 2017/02/22 23:15
0

数据修整

在查询联合中从多个表选择数据 - 最终的结果会是一个扁平的以行为单位的元组。如果你使用了多年的 SQL,你可能认为这种事不会发生在你身上——它导致数据重复,从而使得结果集无法在客户端很好地使用。所以当它发生时往往难以接受。与此相反,LINQ 让你可以获取到休整过的分层级的数据。这就避免了重复,让结果集容易处理,而且在大多数情况下也会消除进行联合操作的必要。例如,假设我们想要提取一组顾客,每一条记录都带上了它们的高价值交易。使用 LINQ,你可以这样做:

from c in db.Customers
where c.Address.State == "WA"
select new
{
   c.Name,
   c.CustomerNumber,
   HighValuePurchases = c.Purchases.Where (p => p.Price > 1000)
}

HighValuePurchases,在这里是一个集合。由于我们查询的是一个相关属性,就不需要进行联合了。因此这是一个内联合还是外联合的细节问题就被很好的抽象掉了。在此例中,当翻译成了 SQL,可能就是一个外联合:LINQ 不会因为子集合返回的是零个元素就排除行。如果我们想要有一个可以翻译成一个内联合的东西,可以这样做:

from c in db.Customers
where c.Address.State == "WA"
let HighValuePurchases = c.Purchases.Where (p => p.Price > 1000)where HighValuePurchases.Any()select new
{
   c.Name,
   c.CustomerNumber,
   HighValuePurchases
}

LINQ 还通过一组丰富的操作符对平面外联合、自联合、组查询以及其它各种不同类型查询进行了支持。

LeoXu
翻译于 2017/02/22 23:31
1

参数化

如果我们想要将之前的例子参数化会如何呢,如此"WA"状态是不是就要来自于一个变量呢? 其实我们只要像下面这样做就可以了:

string state = "WA";

var query =
   from c in db.Customers
   where c.Address.State == state
   ...

不会混淆 DbCommand 对象上面的参数,或者担心 SQL 注入攻击。 LINQ 的参数化是内联、类型安全并且高度可读的。它不仅解决了问题——而且解决得很不错。

因为 LINQ 查询时可以进行组合,所以我们可以有条件的添加谓词。例如,我们写出一个方法,如下:

IQueryable<Customer> GetCustomers (string state, decimal? minPurchase)
{
    var query = Customers.AsQueryable();
    
    if (state != null)
        query = query.Where (c => c.Address.State == state);
    
    if (minPurchase != null)
        query = query.Where (c => c.Purchases.Any (p => p.Price > minPurchase.Value));
    
    return query;
}
LeoXu
翻译于 2017/02/22 23:35
1

如果我们使用空的 state 以及 minPurchase 值调用了这个方法,那么在我们枚举结果集的时候如下 SQL 就会被生成出来:

SELECT [t0].[ID], [t0].[Name], [t0].[AddressID]
FROM [Customer] AS [t0]

不过,如果我们指定了 state 和 minPurchase 的值,LINQ 到 SQL 就不只是向查询添加了谓词,还会有必要的联合语句:

SELECT [t0].[ID], [t0].[Name], [t0].[AddressID]
FROM [Customer] AS [t0]
LEFT OUTER JOIN [Address] AS [t1] ON [t1].[ID] = [t0].[AddressID]
WHERE (EXISTS(
    SELECT NULL AS [EMPTY]
    FROM [Purchase] AS [t2]
    WHERE ([t2].[Price] > @p0) AND ([t2].[CustomerID] = [t0].[ID])
    )) AND ([t1].[State] = @p1)

因为我们的方法返回了一个 IQueryable,查询在枚举到之前并不会被实际地转换成 SQL 并加以执行。这样就给了调用进一步添加谓词、分页、自定义投影等等的机会。

LeoXu
翻译于 2017/02/22 23:41
0

静态类型安全

在之前的查询中,如果我们将 state 变量声明成了一个整型数而不是一个字符串,那么查询可能在编译时就会报错,而不用等到运行时。这个也同样适用于把表名或者列名弄错的情况。这在重构时有一个很实在的好处:如果你没有完成手头的工作,编译器会给出提示。

客户端处理

LINQ 让你可以轻松地将查询的一些部分转移到客户端上进行处理。对于负载负担较大的数据库服务器,这样做可实际提升性能。只要你所取数据没有超过所需(换言之,你还是要在服务器上做过滤),就可以经常性地通过把对结果集进行重新排序、转换以及重组的压力转移到负载较少的应用服务器上去。使用 LINQ,你需要做的就是 AsEnumerable() 转移到查询之中,而自那个点之后的所有事情都可以在本地执行。

LeoXu
翻译于 2017/02/23 00:48
1

什么时候不用 LINQ 去查询数据库

尽管 LINQ 的功能强大,但是它并不能取代 SQL。它可以满足 95% 以上的需求,不过你有时仍然需要SQL:

  • 需要手动调整的查询 (特殊是需要优化和进行锁定提示的时候);

  • 有些涉及到要 select 临时表,然后又要对那些表进行查询操作的查询;

  • 预知的更新以及批量插入操作。

还有就在用到触发器时,你还是需要 SQL。 (尽管在使用 LINQ 的时候诸如此类的东西并非常常被需要,但在要使用存储过程和函数的时候,SQL 是不可或缺的)。你可以通过在 SQL 中编写表值函数来将 SQL 与 LINQ 结合在一起, 然后在更加复杂的 LINQ 查询里面调用这些函数。

了解两门查询语言并不是问题,因为无论如何你都会想要去学习 LINQ 的 — LINQ 在查询本地集合以及 XML DOM 的时候非常实用。如果你使用的仍然是老旧的基于 XmlDocument 的 DOM,LINQ to XML 的 DOM 操作会是一种具有戏剧效果的进步。

还有就是相比于 SQL, LINQ 更易于掌握,所以如果你想写个不错的查询,使用 LINQ 会比 SQL 更好达成。

LeoXu
翻译于 2017/02/24 14:31
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(42)

布客飞龙
布客飞龙

引用来自“乌龟壳”的评论

linq只是另一种sql如hql一样。

文中说的静态类型检查,那只是相对于程序来说的,对于数据库也是静态类型检查,只不过数据库的代码经常是动态由程序生成的。

文中说的查询可以重用,数据库的视图和函数都可以让sql重用。就算是其它基于文本拼装原理的工具也没问题

linq只是一种有编译器静态检查的另一种sql而已,就如同hql那些,没有脱离sql的本质。只是和编程语言结合得比较紧密,可以隐含地借用很多如class和field的数据类型信息,简化和数据库沟通的代码量。

但是并没有超脱sql的范畴,至于文中举得不同数据库sql有差异的问题,其实是微linq实现的翻译功能而已,没什么神奇的,拿一个新类型的数据库,如果查询语句和其它有差异,linq不写翻译代码发布新版本是不可能的。
另一方面如果有必要也可以写从oracle的sql翻译成mysql的翻译器,只是值不值得而已。

sql最大的问题就是把执行计划隐含在里面,对于普通应用没问题,对于复杂应用就像动态语言一样,会是维护的噩梦。
不一样,LINQ 自带 ORM,可以写成链式风格:

db.Customers.Where(c => c.Address.State == "WA").Select(c => new
{
c.Name,
c.CustomerNumber,
HighValuePurchases = c.Purchases.Where (p => p.Price > 1000)
});

dustdragon
dustdragon

引用来自“等待是一生最初的苍老”的评论

说实话,c#是不错得,就是生态圈没java那么好,这个和早些年微软得战略有关系

引用来自“shitalpig”的评论

语法吊打Java,但是Java现成的解决方案多

引用来自“等待是一生最初的苍老”的评论

是啊, 人家java在生态圈的优势时间太长了, java有各种企业级解决方案, 但是c#呢? 这目前是没办法的
这个生态圈确实不咋地,不知道什么时候才能追赶上来
dustdragon
dustdragon
我问个使用LINQ来分页查询的问题,虽然说是到skip和take延迟执行的,但是还是要查询全部的数据,再从里面用skip和take来筛选,这个性能问题难道不是个问题吗,求高手解答
郭大侠写代码
这几年c#给的语法糖太多,这么屌的语言干不过java可惜了
kidfruit
kidfruit

引用来自“进击的代码”的评论

不管怎样,我就喜欢c#的linq
@进击的代码 java8的streamapi也可以尝试哟
kidfruit
kidfruit

引用来自“eechen”的评论

LINQ最终还不是生成SQL交给数据库执行,尽挑些让LINQ看起来简洁的写法来说事,就一个语法糖,还各种鼓吹打败SQL,微软的人呀,就喜欢这种套路.

引用来自“HYUO”的评论

ee神又开始选择性无视了,文章中已经明确说明LinQ相对于SQL不足的地方,但好处也是显而易见的,文中举的例子设计多表链接,汇总和子查询,LinQ的实现明显比SQL简洁。
@HYUO不要理那个废物,直接无视他是最好的选择,可以让这里清净一点。他从来都避重就轻,直只说对自己有利的。事实上他对c#的知识仅限于php相近的几个关键字,就大放厥词,点评江山。
kidfruit
kidfruit

引用来自“乌龟壳”的评论

linq只是另一种sql如hql一样。

文中说的静态类型检查,那只是相对于程序来说的,对于数据库也是静态类型检查,只不过数据库的代码经常是动态由程序生成的。

文中说的查询可以重用,数据库的视图和函数都可以让sql重用。就算是其它基于文本拼装原理的工具也没问题

linq只是一种有编译器静态检查的另一种sql而已,就如同hql那些,没有脱离sql的本质。只是和编程语言结合得比较紧密,可以隐含地借用很多如class和field的数据类型信息,简化和数据库沟通的代码量。

但是并没有超脱sql的范畴,至于文中举得不同数据库sql有差异的问题,其实是微linq实现的翻译功能而已,没什么神奇的,拿一个新类型的数据库,如果查询语句和其它有差异,linq不写翻译代码发布新版本是不可能的。
另一方面如果有必要也可以写从oracle的sql翻译成mysql的翻译器,只是值不值得而已。

sql最大的问题就是把执行计划隐含在里面,对于普通应用没问题,对于复杂应用就像动态语言一样,会是维护的噩梦。

引用来自“joylei”的评论

不敢苟同。LINQ全称Language Integrated Query,在语言层面统一了对信息的查询或处理,不仅仅是XML,集合,数据库,可以说微软的心很大,见https://msdn.microsoft.com/en-us/library/bb308959.aspx。从实现原理上说,本质上只要是实现了IEnumerable或IQueryable接口就支持LINQ查询,编译器会把LINQ查询转化为对扩展方法的调用;IQueryable接口在枚举元素的时候会把相关调用translate成相应的操作,不单单是数据库,可以是ActiveDirectory等等;但是并不限于以上接口,任何实现了Where,Select等扩展方法的类型,都可以使用LINQ查询。所以任何你想象的到的任何操作都可以转化为LINQ查询,只要你写出了对应的一套扩展方法。甚至可以用来写语法分析和词法分析,参见cnblogs上 装配脑袋 的相关博文。
@joylei很多人对linq的理解很狭隘,比如这篇文章,如果一个不了解的人看了以后,会觉得又是一个没啥卵用的hql。实际上linq的应用极其广泛,而且很多时候并没有担心的性能问题。甚至对于基本功不扎实的程序员,linq内部算法反而更优。现在java8的stream,学习c#的做法,思路写法差不多可互通。
进击的代码
进击的代码
不管怎样,我就喜欢c#的linq
d
dexterz

引用来自“乌龟壳”的评论

linq只是另一种sql如hql一样。

文中说的静态类型检查,那只是相对于程序来说的,对于数据库也是静态类型检查,只不过数据库的代码经常是动态由程序生成的。

文中说的查询可以重用,数据库的视图和函数都可以让sql重用。就算是其它基于文本拼装原理的工具也没问题

linq只是一种有编译器静态检查的另一种sql而已,就如同hql那些,没有脱离sql的本质。只是和编程语言结合得比较紧密,可以隐含地借用很多如class和field的数据类型信息,简化和数据库沟通的代码量。

但是并没有超脱sql的范畴,至于文中举得不同数据库sql有差异的问题,其实是微linq实现的翻译功能而已,没什么神奇的,拿一个新类型的数据库,如果查询语句和其它有差异,linq不写翻译代码发布新版本是不可能的。
另一方面如果有必要也可以写从oracle的sql翻译成mysql的翻译器,只是值不值得而已。

sql最大的问题就是把执行计划隐含在里面,对于普通应用没问题,对于复杂应用就像动态语言一样,会是维护的噩梦。

引用来自“joylei”的评论

不敢苟同。LINQ全称Language Integrated Query,在语言层面统一了对信息的查询或处理,不仅仅是XML,集合,数据库,可以说微软的心很大,见https://msdn.microsoft.com/en-us/library/bb308959.aspx。从实现原理上说,本质上只要是实现了IEnumerable或IQueryable接口就支持LINQ查询,编译器会把LINQ查询转化为对扩展方法的调用;IQueryable接口在枚举元素的时候会把相关调用translate成相应的操作,不单单是数据库,可以是ActiveDirectory等等;但是并不限于以上接口,任何实现了Where,Select等扩展方法的类型,都可以使用LINQ查询。所以任何你想象的到的任何操作都可以转化为LINQ查询,只要你写出了对应的一套扩展方法。甚至可以用来写语法分析和词法分析,参见cnblogs上 装配脑袋 的相关博文。
可以去看微软的power query的M语言就知道多强大了。
不避风云
不避风云
不赞同文中观点。比如现在我使用的 PHP Yii 2.0 框架,对于不同数据库及不同版本,都是同样的查询语句,根本不需要 LING。或者说这是另外一种 LINQ吧。
返回顶部
顶部