惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MongoDB | Blog
MongoDB | Blog
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
罗磊的独立博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
SegmentFault 最新的问题
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
博客园 - 司徒正美
博客园 - 叶小钗
T
Tailwind CSS Blog
博客园 - Franky
V
V2EX
有赞技术团队
有赞技术团队
美团技术团队
雷峰网
雷峰网
爱范儿
爱范儿
Jina AI
Jina AI
D
DataBreaches.Net
H
Help Net Security
酷 壳 – CoolShell
酷 壳 – CoolShell

博客园 - 咖啡色

拆解 Claude Code 源码:如何用软件工程思维约束 AI? 为什么要在.Net Core中使用MediatR library? Let’s Encrypt 项目计划自动化地提供免费的 CA 证书 论C#逼格手册 这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧!(第二辑) ASP.NET Page_Load执行两次 .NET:在ASPX、ASHX和MVC中使用IOC容器(菜鸟必看) [转]SqlServer 表分区详细介绍 HttpPostedFileBase always null when upload file by asp.net mvc4 mobile 一步步构建大型网站架构 TCP/IP网络协议的通俗理解,socket,http,soap。 精妙Sql语句备忘 解决.net绘制的 WinForm 在 windows7下变形的方法 关于 ASP.net + JQuery + JQGrid + JSON.net 的简单应用(一) Entity Framework 连接 mysql 5.0出现的问题及解决方案 VSTO开发中VS2010和Office 2003的问题及解决方案 如何使用缓存提高程序性能 如何用Javascript写一个Class? 程序员装逼指南(网上看到的,笑喷了……)
Entity Framework where语句起作用的真正用法
咖啡色 · 2014-07-03 · via 博客园 - 咖啡色

之前对Entity Framework的理解不深,只是图方便,作为linqtosql的替代,但是碰到数据库内容非常多(10万-100万条)时,分页效率很低,用 profiler查看sql语句,才发现其竟然是一次性读取全部数据到内容,然后再执行where之后的语句,真是恐怖。

在网上查询很久,终于发现以下文章,遂记录如下:

这篇文章写完后,发现网上有大量关于Expresstion和Func的讨论,可以不看我的,看这几篇,是一样的,还更深入一些:

http://fascinatedwithsoftware.com/blog/post/2012/01/10/More-on-Expression-vs-Func-with-Entity-Framework.aspx

http://fascinatedwithsoftware.com/blog/post/2011/12/02/Falling-in-Love-with-LINQ-Part-7-Expressions-and-Funcs.aspx

http://stackoverflow.com/questions/793571/why-would-you-use-expressionfunct-rather-than-funct

http://q.cnblogs.com/q/37952/

为什么有这个需求,先看如下两个扩展方法:

public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

这就是我们用Linq, EntityFramework的时候常用的Where方法而已,平时也没去注意过它们的差别:

传入Func,得到的是IEnumerable对象,传入Expression,得到的是IQueryable对象,那么它们之间到底有什么差别?

如下示例:

我要查NorthWind数据库里的Products表里ProductId大于15的所有产品,很简单的一句话,为了分别传入Func和Expression,我们做如下封装:

//接受Func参数,返回IEnumerable
private IEnumerable<T> FetchData2<T>(Func<T, bool> f) where T : class
{
    var context = new NorthwindEntities();
    return context.Set<T>().Where(f);
}
 
//接受Expression<Func>参数,返回IQueryable
private IQueryable<T> FetchData<T>(Expression<Func<T, bool>> f) where T : class
{
    var context = new NorthwindEntities();
    return context.Set<T>().Where(f);
}

分别进行调用:

1 FetchData2<Products>(m => m.ProductID > 15).ToList();
2 FetchData<Products>(m => m.ProductID > 15).ToList();

结果当然没什么区别,我们要的是SQL:

--以Func为参数进行的查询,可见没有生成where语句
SELECT
[Extent1].[ProductID] AS [ProductID], 
[Extent1].[ProductName] AS [ProductName], 
[Extent1].[SupplierID] AS [SupplierID], 
[Extent1].[CategoryID] AS [CategoryID], 
[Extent1].[QuantityPerUnit] AS [QuantityPerUnit], 
[Extent1].[UnitPrice] AS [UnitPrice], 
[Extent1].[UnitsInStock] AS [UnitsInStock], 
[Extent1].[UnitsOnOrder] AS [UnitsOnOrder], 
[Extent1].[ReorderLevel] AS [ReorderLevel], 
[Extent1].[Discontinued] AS [Discontinued]
FROM [dbo].[Products] AS [Extent1]
 
--以Expression为参数进行的查询,如愿生成了where语句
SELECT
[Extent1].[ProductID] AS [ProductID], 
[Extent1].[ProductName] AS [ProductName], 
[Extent1].[SupplierID] AS [SupplierID], 
[Extent1].[CategoryID] AS [CategoryID], 
[Extent1].[QuantityPerUnit] AS [QuantityPerUnit], 
[Extent1].[UnitPrice] AS [UnitPrice], 
[Extent1].[UnitsInStock] AS [UnitsInStock], 
[Extent1].[UnitsOnOrder] AS [UnitsOnOrder], 
[Extent1].[ReorderLevel] AS [ReorderLevel], 
[Extent1].[Discontinued] AS [Discontinued]
FROM [dbo].[Products] AS [Extent1]
WHERE [Extent1].[ProductID] > 15

这个结果是惊人的,居然传入func的情况下是把数据全部取到内存里再进行枚举过滤(linq to entity)~~~,这一下吃惊不小,可见平时的开发中这一点是必须要时刻注意的。

其实这就是Linq to Sql和Linq to Entity的区别,由于重载的原因,都做到同样的方法上来,一旦参数传错了,就是别的方法了。

比较还没完,因为一般的企业框架或接口,是不会暴露底层数据库操作出来的,对外的都是业务方法,比如我们上面的示例,目的是获取ID大于某值的所有产品,那么我们假定暴露成如下方法(接口):

//获取ID大于某值的产品列表
private IEnumerable<Products> GetProducts(int id)
{
    var datas = FetchData<Products>(m => m.ProductID > id);
    return datas;
}

调用:

生成SQL:

exec sp_executesql N'SELECT 
[Extent1].[ProductID] AS [ProductID], 
[Extent1].[ProductName] AS [ProductName], 
[Extent1].[SupplierID] AS [SupplierID], 
[Extent1].[CategoryID] AS [CategoryID], 
[Extent1].[QuantityPerUnit] AS [QuantityPerUnit], 
[Extent1].[UnitPrice] AS [UnitPrice], 
[Extent1].[UnitsInStock] AS [UnitsInStock], 
[Extent1].[UnitsOnOrder] AS [UnitsOnOrder], 
[Extent1].[ReorderLevel] AS [ReorderLevel], 
[Extent1].[Discontinued] AS [Discontinued]
FROM [dbo].[Products] AS [Extent1]
WHERE [Extent1].[ProductID] > @p__linq__0',N'@p__linq__0 int',@p__linq__0=15

可以看一下跟上面生成的sql的差别,已经变成存储过程了。

好吧,其实说到这里还没点题,上面只是说到了几种不同语法的差别,但是为什么有我标题这一说呢?因为来自于我的项目底层的某个封装:

IEnumerable<T> GetEntities(Func<T, bool> exp);

显然我用了Func传参,结果你们也知道了,于是我开始寻找Func转Expression的方法,一遍海搜之下,用了各种转化方式,才发现如下两句话都是成立的:

Expression<Func<Products, bool>> g = m => m.ProductID > 15;
Func<Products, bool> t = m => m.ProductID > 15;

但是把t转成g却不容易,再多看一下,既然实现体是一样,其实也就是声明不一样了,那么直接更改参数声明不就可以了么?上面已经演示过了,不需要任何硬编码,直接生效。