实体框架6与Dapper,存储过程与SQL Server中的请求

时间:2019-03-17 09:49:06

标签: sql-server database entity-framework linq dapper

我的项目中使用LINQ和SQL Server的EF很长时间了。

我正在寻找一种方法来提高对数据库的请求的性能。我阅读了很多有关Dapper和Procedures的信息,它比EF更快。我将Dapper添加到项目中,添加了过程...。但是我的测试显示出奇怪的结果。 EF和Dapper以及存储过程的结果几乎相同-没有好处。

首先,我检查了一个包含大量Join的请求。在Dapper,Procedure和EF之间,我得到的结果几乎相同。然后,我决定使用一个没有关系的简单表进行测试。

我有表格ZipCodes。有43200条记录。

ZipCode table

我在SQL Server中使用EF,Dapper,存储过程和请求对1000条记录,10000条记录和43200条记录进行了测试。

Dapper

string query =
            "SELECT TOP (43200) [Zip]\r\n      ,[City]\r\n      ,[State]\r\n      ,[Latitude]\r\n      ,[Longitude]\r\n      ,[TimeZone]\r\n      ,[DST]\r\n  FROM [dbo].[ZipCodes]";

using (IDbConnection connection = new SqlConnection(_connectionString))
{
    var result = connection.QueryAsync<ZipCodes>(query).Result.ToList();
    return result;
}

EF

var zip = db.ZipCodes.AsNoTracking().Take(43200).ToList();

存储过程

ALTER PROCEDURE [dbo].[ZIPTest]
AS 
BEGIN 
    SELECT TOP (43200) 
        [Zip], [City], [State], [Latitude], [Longitude], [TimeZone], [DST] 
    FROM 
        [dbo].[ZipCodes] 
END

在SQL Server中有时间的请求

SELECT GETDATE(); 

SELECT TOP (43200) 
    [Zip], [City], [State], [Latitude], [Longitude], [TimeZone], [DST]
FROM 
    [dbo].[ZipCodes]

SELECT GETDATE();

在代码中,我使用秒表

string first = "", second = "", third="";
System.Diagnostics.Stopwatch swatch = new System.Diagnostics.Stopwatch();
swatch = new Stopwatch();
swatch.Start(); Dapper request;

然后

swatch.Stop();
first = swatch.Elapsed.ToString(@"m\:ss\.fff");
swatch = new Stopwatch();
swatch.Start();

依此类推

结果:(以毫秒为单位)

                       1000     10000      43200
-------------------------------------------------
EF                      107      1085       4527
Dapper                  139      1084       4036
Stored procedure        129      1089       4519
SQL query                 8        17         60

EF,Dapper和存储过程之间的差异很小。为什么会这样?

为什么SQL Server中的查询如此之快,而来自代码的请求却慢15-70倍?

可以吗?

2 个答案:

答案 0 :(得分:1)

使用EF表现出性能问题的代码在Dapper或ADO + Sprocs下不会神奇地运行得更快。要深入了解性能问题,您需要调查并消除导致性能问题的原因。

在最高级别,这些性能问题源于两个核心问题。

  • 加载太多数据。
  • 过于频繁地加载数据。

我要注意的关键事项:(一开始,有很多东西,但这些都是大赢家)

  1. 惰性加载:这是代码从一组相关实体中进行加载的位置,但是这样做是在初始加载后代码正在访问这些相关实体,从而导致每个相关实体都被分别加载

    • 狩猎方法:将SQL事件探查器与仅运行该应用程序的调试实例的数据库挂钩。 (即本地数据库)
    • 症状:在加载单个实体或集合的主查询之后,看到很多“ SELECT TOP(1)...”查询。
    • 修复:快速解决方案是引入热切的加载(.Include())以加载这些集合。更好的解决方法是使用.Select()来加载相关代码所需的属性。
  2. .ToList():错位的.ToList()调用会随着系统的成熟而引起巨大的性能头痛,因为开发人员遇到了EF问题,该问题可以通过调用.ToList来解决。通常,这些出现在开发人员尝试在.Where().Select()表达式内调用方法时出现。 EF无法理解这些内容以传递SQL等效项,因此添加.ToList()会将其转换为Linq2Object,并且“ ta-da”可以正常工作!

    • 狩猎方法:寻找.ToList()的实例,并标记任何在.ToList().Select()等之前找到.Where()的情况。
    • 症状:删除多余的.ToList()会导致EF错误。
    • 修复:检查有问题的函数是否具有等效的DbFunctions。一个常见的问题是使用DateTime函数,该函数可以在DbFunctions中找到。在其他情况下,找到有问题的函数并为正在选择的预期数据创建一个视图模型,然后创建一个属性以在视图模型中运行该函数。
  3. 客户端分页+实体:如果没有适当的示例数据,开发的另一个缺点。编写查询可以有效地返回所有数据,而无需考虑记录总数。数据通过分页显示在客户端,可以“工作”,但速度确实很慢。当数据库中只有200行时,它可以正常工作,但是现在可以爬到50,000行。 (而且只会变得更糟)

    • 狩猎方法:查看返回集合的任何API / Controller方法。这些查询是否使用.ToList().Skip() + .Take()?这些方法会返回实体还是查看模型?
    • 症状:分页列表的加载速度确实很慢。加载后,页面切换很快。
    • 修复:来自分页控件的调用应修改为使用服务器端分页。这意味着将排序,页面大小和页面号信息发送到服务器调用。这样,可以使EF查询更高效,从而仅加载控件需要显示的行数。该代码还应该返回搜索结果的视图模型,这些视图模型仅显示显示的列,以及按需加载完整实体所需的键。 (例如,当用户单击以打开记录时。)当您只需要显示少数字段时,实体就可以成为重量级对象。
  4. 数据库索引编制:是否已监视和维护数据库?是否存在索引和索引维护?对于SQL Server,是否正在备份数据库并缩小了Tx日志? Code First实现充斥着这些问题,在这些问题中,系统在不考虑备份数据库的情况下可以正常运行。随着系统的发展,不会在乎支持它的数据库。

    • 狩猎方法:您是否有专门的DBA来照料数据库并清楚地表明它正在执行从头开始?
    • 症状:没有对数据库进行DBA或检查。例如,应用程序使用的是GUID PK,它们设置为NEWID()或Guid.New(),而无需任何索引维护。数据库未设置索引。事务日志(.LDF)比数据库文件(.MDF)等等等大15倍。
    • 修正:雇用DBA。如果您使用的是GUID键,请切换到NEWSEQUENTIALID()并建立一些计划的索引维护作业。

答案 1 :(得分:0)

您在应用程序中获得的性能受许多因素影响,但是从数据库中获取数据基本上可以分为3部分:

  • 查询运行时间
  • 数据传输时间
  • 客户处理时间

这就是说,如果您的最高成本是在数据库本身或数据传输中,那么从FE切换到Dapper或切换到简单的数据读取器毫无意义。但是,如果您的客户端应用程序的成本最高(高并发性/少量有效负载/大量连接(linq)/要映射的多个数据集/多个列),那么从FE切换到dapper或切换到datareader就是有意义的。但是,您需要了解,将以易用性换取性能。在时间紧迫的应用程序中,在大多数现实世界中,这可能没有道理。