点击上方蓝字关注“汪宇杰博客”

这是一篇我在2012年写的老文章,至今适用(没错,我说的就是适用于EF Core)。因此使用微信重新推送,希望能帮到大家。

自从我用了EF,每次都很关心是否有潜在的性能问题。所以每次我写LINQ查询,都会使用SQL Profiler看一下实际生成的SQL语句,以便发现潜在的性能问题。也强烈建议大家这么去做,以免日后软件大了出了问题很难查。

只选择某列或某些列

有些时候,在C#里写LINQ虽然看着舒服,但性能不一定好,所以有必要做一些调整。比如这种情况:

我需要知道一篇文章的点击数,仅此而已,我可能会写:

context.Post.FirstOrDefault(p => p.Id == postId).Hits;

或者:

context.Post.Find(postId).Hits;

我期待着他们只去数据库里筛选Hits这一列的数据,然而,通过SQL Profiler会发现,这两条语句居然把全部列都给select出来了,访问Hits的操作实际是在内存中进行的

虽然小表看不出性能问题,但万一你的表里有一列是存文件字节流(byte)的,那这样的操作可能会很慢,并且消耗额外的网络传输,所以不能忽视这个问题。

其实,我只要稍作调整,就能避免这个问题,但会LINQ语句难看一点:

context.Post.Where(p => p.Id == postId).Select(p => p.Hits).FirstOrDefault();

最终生成的native sql是这样的:

exec sp_executesql N'SELECT TOP (1) [Extent1].[Hits] AS [Hits]FROM [dbo].[Post] AS [Extent1]WHERE [Extent1].[Id] = @p__linq__0',N'@p__linq__0 uniqueidentifier',@p__linq__0='850C3A86-6C3D-408B-8099-61EDA559F804'

真正的只select了Hits一个字段。

ToList()的问题

其实EF很多时候的性能问题都是关系到查询执行时机的。我们通常的意图是,首先建立一个查询表达式,只是build,而不execute。执行的时机是用到这个表达式结果的时候才去执行。

在公司码程序的时候,我看到好多同事用EF,写完查询喜欢直接调用ToList()方法。有时候这会造成很大的性能问题。因为单纯声明一个linq表达式并不会立即执行SQL查询,然而一旦在后面加上ToList(),就会立即去执行。如果你只是想根据条件选择其中一些数据,而非全部的话,那ToList()以后再筛选,就是从内存里执行了,并不是把你的条件转换成sql的where语句去执行。

var query = from ..... // 建立查询,但不执行

var result = query.ToList(); // 立即执行查询

所以,你应当尽量避免从ToList()后的结果中再去查找自己想要的元素

IQueryable, IEnumerable

在这两个接口的选择上,我偏向使用IQueryable。大部分时候这两个接口在使用上的表现都是一致的,但如果你要做的是一个不确定的查询,意思是这个查询表达式不是一次性确定的,对于它的结果可能由别的类来选择到底select哪些东西,这时候就要用IQueryable

比如我有一个数据层方法:

public IEnumerable<EdiBlog.Core.Entities.Post> GetAllPost()

{

return context.Post;

}

很显然,它会被系统中的其他方法调用,而这些调用者希望得到的结果都各不相同。通常的操作就是再拼一个where语句上去:

var myResult = postDa.GetAllPost().Where(...)

但这时,很不幸的是,where语句中的条件并不是转换为native sql去执行的,它是在内存中筛选的。这是一个比较阴的性能问题。所以文章一开始我就建议大家多用SQL Profiler看看自己的LINQ是怎么执行的。

如果把返回类型换成IQueryable,那么你的where语句就可以转化为SQL执行。

public IQueryable<EdiBlog.Core.Entities.Post> GetAllPost()

{

return context.Post;

}

关于这两个接口,在StackOverflow上有一个比较好的帖子,大家可以自己看一下:

http://stackoverflow.com/questions/252785/what-is-the-difference-between-iqueryablet-and-ienumerablet

“IEnumerable: IEnumerable is best suitable for working with in-memory collection. IEnumerable doesn’t move between items, it is forward only collection.

IQueryable: IQueryable best suits for remote data source, like a database or web service. IQueryable is a very powerful feature that enables a variety of interesting deferred execution scenarios (like paging and composition based queries).”

在MSDN论坛上也有个比较直观的答案:

IQueryable returns a "queryable" that is a query you could still be enriched before really sending it to the server.

IEnumerable returns a list that is the actual querying took place and you get the results. ToList is isued to force running the query and returning these enumerable results...

So in short :
- use IQueryable if you want to return a base query that could be further enhanced before running it server side (by enumerating its items)..
- use IEnumerable/ToList if you want to return a list that has been retrieved from the db

计算个数,Count()和Count

这个是最容易被坑,也是非常严重的一个性能问题。当我们需要统计符合某条件的记录的条数时,我们希望SQL语句是SELECT COUNT(*) ... 这种形式的。然而下面这个看似很自然的写法却会导致不希望的结果:

context.Category.FirstOrDefault(p => p.Name == categoryName).Posts.Count;

这是我博客里用来统计某分类下文章数目的语句,当然,因为发现性能问题,现在已经不是这么写了。它产生的SQL并不是SELECT COUNT,而是分成2条。下面是SQL Profiler抓到的:

exec sp_executesql N'SELECT TOP (1) [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[DisplayName] AS [DisplayName]FROM [dbo].[Category] AS [Extent1]WHERE [Extent1].[Name] = @p__linq__0',N'@p__linq__0 nvarchar(4000)',@p__linq__0=N'ASPNET'exec sp_executesql N'SELECT [Extent2].[Id] AS [Id], [Extent2].[Title] AS [Title], [Extent2].[Slug] AS [Slug], [Extent2].[PubDate] AS [PubDate], [Extent2].[PostContent] AS [PostContent], [Extent2].[Author] AS [Author], [Extent2].[CommentEnabled] AS [CommentEnabled], [Extent2].[IsPublished] AS [IsPublished], [Extent2].[Hits] AS [Hits], [Extent2].[Rators] AS [Rators], [Extent2].[Rating] AS [Rating], [Extent2].[ExposedToSiteMap] AS [ExposedToSiteMap], [Extent2].[DisplayFrom] AS [DisplayFrom], [Extent2].[DisplayTill] AS [DisplayTill], [Extent2].[LastModifyOn] AS [LastModifyOn], [Extent2].[PublishToRss] AS [PublishToRss]FROM  [dbo].[PostCategory] AS [Extent1]INNER JOIN [dbo].[Post] AS [Extent2] ON [Extent1].[PostId] = [Extent2].[Id]WHERE [Extent1].[CategoryId] = @EntityKeyValue1',N'@EntityKeyValue1 uniqueidentifier',@EntityKeyValue1='3FEB11A2-6E36-4DCE-8C02-614BEF7ACC62'

可以看到,EF做了两件事,第一件事是查找Name为"ASPNET"的Category,然后用这个Category的Id去找它所有的Post,最后做Count的其实是.NET在内存里进行的。这显然把我们不需要的信息都给SELECT出来了。我们只需要一个Count,为毛会这么复杂呢?

回顾第一条我所讲过的。不难发现。在FirstOrDefault(...)之后访问的属性,都是在内存里进行的。所以,当我们访问Category.FirstOrDefault(p => p.Name == categoryName)的时候,就生成了第一条SQL语句。紧跟其后的“.Posts”是Category对象的导航属性,EF会用lazy load去加载这个category所有的post,所以就生成了第二条SQL语句。再紧接其后的Count就自然而然在内存里进行了。

如果要让代码尽量去生成LINQ to SQL,有个很简单的原则,就是尽量用LINQ、Lambda表达式,这样EF才可能帮我们翻译。C#里的Count有两种。Enumerable.Count()是方法,List.Count是属性。一旦一个东西变成了List,你再去Count,就必定是在内存里进行的了。

所以,在EF中,要进行Count操作,应该这样写:

context.Post.Count(p => p.Categories.Any(q => q.Name == categoryName));

这时,Count()接受了一个lambda表达式,LINQ to SQL就能准确翻译为“SELECT COUNT”了:

SELECT [GroupBy1].[A1]  AS [C1]

FROM   (

SELECT COUNT(1)      AS [A1]

FROM   [dbo].[Post]  AS [Extent1]

WHERE  EXISTS (

SELECT 1 AS [C1]

FROM   [dbo].[PostCategory] AS [Extent2]

INNER JOIN [dbo].[Category] AS [Extent3]

ON  [Extent3].[Id] = [Extent2].[CategoryId]

WHERE  ([Extent1].[Id] = [Extent2].[PostId])

AND ([Extent3].[Name] = 'ASPNET')

)

)                AS [GroupBy1]

现在性能要明显好很多~

.NET编程委提醒您

ORM千万种,EF最方便,使用不规范,性能两行泪

Entity Framework 的一些性能建议相关推荐

  1. Entity Framework Core 6.0 预览4 性能改进

    起因 微软在Build2021开发者大会上,发布Entity Framework Core 6.0(简称EFCore 6)预览第四版,号称是性能版本,性能提升主要对于Entity Framework  ...

  2. 【转】学习Entity Framework 中的Code First

    这是上周就写好的文章,是在公司浩哥的建议下写的,本来是部门里面分享求创新用的,这里贴出来分享给大家. 最近在对MVC的学习过程中,接触到了Code First这种新的设计模式,感觉很新颖,并且也体验到 ...

  3. 彻底征服 Entity Framework Core 优化!

    作者 | 喵叔 责编 | 胡巍巍 出品 | CSDN(ID:CSDNnews) 这篇文章我们来讲解一下 Entity Framework Core 的优化方案.Entity Framework Cor ...

  4. 提高Entity Framework性能的一些建议

    LINQ to Entity是用于查询和管理数据库的极好的ORM. 它提供了很多东西,所以必须了解它的性能.LINQ在某一些地方有它自己的坏处. 在使用Entity Framework ORM设计和查 ...

  5. ADO.net,Linq to SQL和Entity Framework性能实测分析

    [测试总结] 第一阶段测试结果非常出人意料,ADO.net和LINQ to SQL操作数据的时间都控制在0.5秒以内,非常的迅速,但是Entity Framework在添加这步表现非常差,由于这五步是 ...

  6. Entity Framework 学习建议及自学资源

    Entity Framework 学习建议及教学PPT 金旭亮 =============================================== EntityFramework(EF)是 ...

  7. .NET Entity Framework入门简介及简单操作

    Entity Framework是微软借鉴ORM思想开发自己的一个ORM框架. ORM就是将数据库表与实体对象(相当于三层中的Model类)相互映射的一种思想. 最大的优点就是非常方便的跨数据库平台. ...

  8. [转]Entity Framework走马观花之把握全局

    学习Entity Framework技术期间查阅的优秀文章,出于以后方便查阅的缘故,转载至Blog,可查阅原文:http://blog.csdn.net/bitfan/article/details/ ...

  9. Entity Framework part2

    EF原理 以XML方式打开edmx文件,这个XML的文件主要包含两大部分:Runtime是类模型部分,Designer是VS中的图形界面 重点讨论的是Runtime部分,又分为三大部分: SSDL数据 ...

最新文章

  1. dev c++自动排版_再也不头疼文字排版了
  2. openjudge-NOI 2.6-1759 最长上升子序列
  3. [复习]莫比乌斯反演,杜教筛,min_25筛
  4. 2010-04-25 搞定aftr
  5. 除了架构,没有数据人才,也做不了数字化转型,传统企业路在何方
  6. mysql基于ssl安全连接的主从复制
  7. Linux 系统中的超级权限的控制
  8. mysql存表情出错的解决方案(类似\xF0\x9F\x98\x86\xF0\x9F)
  9. Net设计模式实例之享元模式( Flyweight Pattern)(1)
  10. 让代码更美:10大编程字体
  11. 《软件工程》 课后思考题
  12. linux 硬件raid 坏道,Linux服务器磁盘坏道的修复过程
  13. matlab 画地图 经纬度,MATLAB绘制地图.docx
  14. python父亲节祝福_2020年精选优美的父亲节祝福语28条
  15. Redis RDB和AOF
  16. java solr group by_solr高级查询——group和facet
  17. SqlDbx远程链接DB2数据库
  18. 活水决策体系七:辩证思维之三大规律
  19. STANet简单介绍
  20. linux查看文件命令

热门文章

  1. matlab胡良剑第五章,MATLAB习题参考答案(胡良剑,孙晓君)
  2. 前端进阶之路: 前端架构设计(2)-流程核心
  3. 计算机与操作系统基础小结
  4. 清北·NOIP2017济南考前冲刺班 DAY1 morning
  5. 小程序 - 学习笔记
  6. 《BeagleBone开发指南》——1.7 小结
  7. Eclipse C++的配置问题launch failed binary not found
  8. APP-V5.0的Sequencer过程
  9. Win11推送加速!
  10. 面试官:实现一个带值变更通知能力的Dictionary