首页 > 其他分享 >使用EF框架的优化(四)

使用EF框架的优化(四)

时间:2024-04-30 15:45:10浏览次数:29  
标签:Count __ Extent2 框架 IQueryable EF SQL 优化 Extent1

EF容易忽略的优化点-能转换成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。执行的时机是用到这个表达式结果的时候才去执行
在公司码程序的时候,写完查询喜欢直接调用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

IQueryable返回一个“queryable”,这是一个在真正将其发送到服务器之前仍然可以丰富的查询。
IEnumerable返回一个列表,该列表是实际进行的查询,您将获得结果。ToList用于强制运行查询并返回这些可枚举的结果。。。
简而言之:
-如果要返回基本查询,则使用IQueryable,该基本查询可以在服务器端运行(通过枚举其项)之前得到进一步增强。。
-如果要返回从数据库检索到的列表,请使用IEnumerable/ToList

计算个数,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最方便,使用不规范,性能两行泪

标签:Count,__,Extent2,框架,IQueryable,EF,SQL,优化,Extent1
From: https://www.cnblogs.com/liuqifeng/p/18168129

相关文章

  • Unity游戏框架设计之音频管理器
    Unity游戏框架设计之音频管理器简单介绍在游戏中,主要的音频分为两种类型。第一种类型是BGM(背景音乐),第二种类型是音效。BGM的特点是音频持续时间长,一般在游戏中的同一时间内只能播放一个BGM。音效的特点是音频持续时间短,一般在游戏中的同一时间内允许同时播放多个音效。在下......
  • 好用的自动同步软件:FreeFileSync
    最近研究怎么能够将文件从电脑上自动备份到移动硬盘,发现了很多人都在安利的FreeFileSync(官网:FreeFileSync )FreeFileSync​freefilesync.org/这是一个免费软件,在各个系统中都有提供安装包。软件安装过程非常简单,目前我将其直接安装到了C盘,安装好后即可看到两个软件:绿色的FreeFil......
  • Unity游戏框架设计之场景管理器
    Unity游戏框架设计之场景管理器简单介绍在游戏开发过程中,我们经常对Scene进行切换。为了不使场景切换时造成的游戏卡顿,可以Unity官方API利用协程的方式异步加载场景。同时,为提升Scene切换的玩家体验,我们经常会在场景切换的开始,先显示过渡UI,然后才对目标场景进行加载......
  • react中什么使用定义变量,需要使用useRef,什么时候直接定义即可?
    在React中,决定使用useState、useRef或是直接定义变量,主要取决于你希望变量如何与组件的渲染周期互动以及是否需要持久化某些值。直接定义变量场景:如果你只是需要一个在函数内部临时存储数据的变量,而且这个变量的值不需要在组件重新渲染时保持不变,或者它并不直接影响UI的渲染逻......
  • react中如何区分什么场景下应该使用useEffect,什么场景下应该使用发布订阅模式,进行通信
    在React中,useEffect和发布订阅模式(Pub/Sub)解决的是不同层面的问题,因此它们的使用场景也有所不同。useEffect的使用场景数据获取:当组件挂载后需要从服务器获取数据时,可以使用useEffect。同时,当依赖项发生变化时,它也能帮助重新获取数据。副作用处理:任何需要在渲染之外进行的......
  • Go语言系列——Go协程、信道(channel)、缓冲信道和工作池、Select、Mutex、结构体取代类
    文章目录21-Go协程Go协程是什么?Go协程相比于线程的优势如何启动一个Go协程?启动多个Go协程22-信道(channel)什么是信道?信道的声明通过信道进行发送和接收发送与接收默认是阻塞的信道的代码示例信道的另一个示例死锁单向信道关闭信道和使用forrange遍历信道23-缓冲信......
  • spring-web-mvc项目运行报错:java.lang.NoClassDefFoundError: org/apache/catalina/We
    使用idea运行spring-web-mvc项目时,报错如下:错误:无法初始化主类com.itranswarp.learnjava1.AppConfig原因:java.lang.NoClassDefFoundError:org/apache/catalina/WebResourceRoot 首先,保证当前项目在独立的项目中打开,不要在多个项目中运行。导入Maven工程要选择:File......
  • Reflexion: Language Agents with Verbal Reinforcement Learning
    发表时间:2023(NeurIPS2023)文章要点:文章提出Reflexion框架,通过交互的方式获得反馈,并变成细致的语言feedback的形式作为下一轮的prompt,以此强化languageagents的能力,同时避免了更新大模型的参数。这样的好处有1)轻量,不需要finetuneLLM,2)feedback相比一个reward信号更加细致,3)充分......
  • 使用EF框架的优化(三)-大量数据查询
    在C#中使用EF查询数据库数据,将结果直接转换为List是一个常见的做法。这样做的好处是方便快捷,可以方便地对结果集进行操作和遍历。但是也存在一些问题:1.性能问题:将查询结果直接转换为List会加载整个结果集到内存中,如果查询结果较大,可能会导致性能问题和内存占用过高。2.内存管......
  • openGauss 内存优化表MOT管理
    内存表特性MOT介绍使用MOTMOT的概念附录详情查看:https://opengauss.org详情查看:https://docs-opengauss.osinfra.cn......