Entity Framework Code First 在Object Join Linq查询时出现全表查询的语句。

时间:2022-02-15 13:02:26

最近一个项目,使用微软的Entity Framework的ORM框架的项目,部署到现场后,出现了系统缓慢,多个客户端的内存溢出崩溃的问题。

打开了SQL Server Profiler(SQL Server Profiler的简单使用)排查,发现有全表查询的语句,这表中有上万条数据,所以客户端查询后内存溢出了。

从代码中排查是否有直接全表查询的语句,结果未找到,后来在网上搜索到Linq to Object 连接(join) Linq to Entity时可能会引起全表查询。(https://www.cnblogs.com/gxlinhai/p/4263393.html)

 

所以这里也创建了一个demo做了进一步的了解。

 

此Demo 使用Entity Framework的ORM框架,只需要配置数据库连接,能自动生成数据库,并创建一些数据。

 

表有Entity和Foo两个表,其中Foo表中有Entity表的外键。

打开SQL Server Profiler检测生成的语句。

测试一,用IEnumerable来连表查询。

                IEnumerable<Foo> foos = db.Foos; 
                var objectNames = (from foo in foos
                                   join en in db.Entitys
                                   on foo.EntityId equals en.EntityId
                                   select foo.Name).ToList();

之后在SQL Server Profiler中监测到如下语句:

SELECT 
    [Extent1].[EntityId] AS [EntityId], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[Notes] AS [Notes]
    FROM [dbo].[Entities] AS [Extent1]

 出现了Entity表全表查询的语句。

 

测试二, 用var来连表查询。

                var foos = db.Foos; 
                var objectNames = (from foo in foos
                                   join en in db.Entitys
                                   on foo.EntityId equals en.EntityId
                                   select foo.Name).ToList();

 在SQL Server Profiler中,监测到的语句如下

SELECT 
    [Extent1].[Name] AS [Name]
    FROM  [dbo].[Foos] AS [Extent1]
    INNER JOIN [dbo].[Entities] AS [Extent2] ON [Extent1].[EntityId] = [Extent2].[EntityId]

 没有出现全表查询的语句。

 

测试三,用IQueryable来连表查询

IQueryable<Foo> foos = db.Foos; 
var objectNames = (from foo in foos
                  join en in db.Entitys
                   on foo.EntityId equals en.EntityId
                   select foo.Name).ToList();

 在SQL Server Profiler中,监测到的语句如下

SELECT 
    [Extent1].[Name] AS [Name]
    FROM  [dbo].[Foos] AS [Extent1]
    INNER JOIN [dbo].[Entities] AS [Extent2] ON [Extent1].[EntityId] = [Extent2].[EntityId]

 没有出现全表查询的语句。

 

测试四:结合别人说的代码,修改如下

 List<MyObject> objectList = new List<MyObject>();
                objectList.Add(new MyObject { Identity = 1, Name = "Jack", Age = 30 });
                objectList.Add(new MyObject { Identity = 2, Name = "Sam", Age = 28 });
                objectList.Add(new MyObject { Identity = 3, Name = "Lucy", Age = 23 });

                var objectNames = (from foo in objectList
                                   join en in db.Entitys
                                   on foo.Identity equals en.EntityId
                                   select foo.Name).ToList();

 在SQL Server Profiler中,监测到的语句如下

SELECT 
    [Extent1].[EntityId] AS [EntityId], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[Notes] AS [Notes]
    FROM [dbo].[Entities] AS [Extent1]

 又出现了Entity全表查询的语句。

 

结论:

测试1:返回结果为IEnumerable时,有可能数据已经缓存到内存里了,作为了object,这样再linq查询时,不会重新调整sql语句,导致了连接表全表查询后在内存中过滤。

测试2、3: IQueryable在连表查询时,最后会重新生成sql语句来查询。var默认为DbSet,DbSet也会重新生成sql语句来查询。

测试4: 直接证明了object与linq查询,导致查询语句会变成全表查询。

 

Demo见如下:

百度网盘:https://pan.baidu.com/s/1cDEIS2

提取密码:qwnb