Dapper的QueryFirstorDeFault方法真的很慢吗?

发布于 2025-01-18 18:01:59 字数 2060 浏览 3 评论 0原文

我读到Dapper比EF快(至少在检索数据时)要快,我想确认这一点,因此我在BenchmarkDotnet的帮助下比较了Dapper和EntityFramework。

所以我尝试了这个...

    [Benchmark]
    public Player EntityFramework_GetByName()
    {
        using (ApplicationDbContext context = new())
        {
            return context.Players.FirstOrDefault(x => x.FirstName == _name);
        }
    }

    [Benchmark]
    public Player Dapper_GetByName()
    {
        using (SqlConnection conn = new(Database.ConnectionString))
        {
            return conn.QueryFirstOrDefault<Player>($"SELECT * FROM Players WHERE FirstName = '{_name}'");
        }
    }

但是结果不是我的期望...

然后我阅读在这里关于列类型“问题”,以及如何影响性能,因此我将列的类型更改为NVarchar,最大长度为100,而我的代码为dapper为此,

    [Benchmark]
    public Player Dapper_GetByName()
    {
        using (SqlConnection conn = new(Database.ConnectionString))
        {
            return conn.QueryFirstOrDefault<Player>($"SELECT * FROM Players WHERE FirstName = @name", new 
            { @name = new DbString { Value = _name, IsAnsi = false } });
        }
    }

基准测试的结果如下..

方法平均误差STDDEV分配的
Dapper_getByName41,092.8 US1,400.39 US4,085.0 US US4 KB
ENTITYFRAMEWORKWORK_GETBYWORKERWORK_GETBYNAME2,971.6 US305.43 US 305.43 US895.8 US 110 KB US110 KB

。有没有办法改善这一点?

I read that Dapper is faster than EF (at least at retrieving data) and I want to confirm that so I am comparing Dapper and EntityFramework with the help of BenchmarkDotNet.

So I tried this...

    [Benchmark]
    public Player EntityFramework_GetByName()
    {
        using (ApplicationDbContext context = new())
        {
            return context.Players.FirstOrDefault(x => x.FirstName == _name);
        }
    }

    [Benchmark]
    public Player Dapper_GetByName()
    {
        using (SqlConnection conn = new(Database.ConnectionString))
        {
            return conn.QueryFirstOrDefault<Player>(
quot;SELECT * FROM Players WHERE FirstName = '{_name}'");
        }
    }

But the result are not what I expecting...

Then I read here about the column type "problem" and how that can affect the performance, so I change the type of the column to NVarchar with max length of 100 and my code for the Dapper to this

    [Benchmark]
    public Player Dapper_GetByName()
    {
        using (SqlConnection conn = new(Database.ConnectionString))
        {
            return conn.QueryFirstOrDefault<Player>(
quot;SELECT * FROM Players WHERE FirstName = @name", new 
            { @name = new DbString { Value = _name, IsAnsi = false } });
        }
    }

The results of the benchmark tests are the following..

MethodMeanErrorStdDevAllocated
Dapper_GetByName41,092.8 us1,400.39 us4,085.0 us4 KB
EntityFramework_GetByName2,971.6 us305.43 us895.8 us110 KB

The difference is very big. Is there a way to improve this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

小红帽 2025-01-25 18:01:59

我认为这个例子非常清楚地展示了直接使用 Dapper、CA.Blocks.DataAccess 或 ADO.NET 时 SQL 查询生成的责任。当使用这些包访问数据库时,开发人员完全负责 SQL 查询、其投影和执行。使用 EF 时,生成查询的责任不再由开发人员承担,而是委托给 EF。这是一把双刃剑,可能会产生良好的查询,也可能会产生非常糟糕的查询。 Dapper 中的大部分性能提升都来自于对 SQL 的完全控制和消除不良 SQL 的生成。反之亦然,与 EF 相比,Dapper 的大多数性能问题都是由于 EF 创建了更好的查询。
那么这里发生了什么。简单来说,EF 已经查看了请求,并知道您只知道第一条记录 FirstOrDefault,因此它的查询生成导致

SELECT TOP 1 * FROM … WHERE…

您正在进行比较的 Dapper 查询是,

SELECT * FROM … WHERE …

所以我怀疑差异纯粹在于 SQL。使用的Test数据库,可能在Person表中有很多记录。给定这些数字,名称上可能没有索引,从而导致表扫描以查找匹配的数据。

在 EF 生成的查询中,数据库可以在找到第一条记录后立即停止执行,在 Dapper 示例中,数据库根据名称组装包含所有匹配项的完整记录集,然后发送该行集。 Dapper 只是读取第一行并关闭连接。

为了进行公平的比较,您需要将查询更改为 top 1。

 [Benchmark]
    public Player Dapper_GetByName()
    {
        using (SqlConnection conn = new(Database.ConnectionString))
        {
            return conn.QueryFirstOrDefault<Player>($"SELECT Top 1 * FROM Players WHERE FirstName = @name", new 
            { @name = new DbString { Value = _name, IsAnsi = false } });
        }
    }

同样,决定使用 Dapper 来提高性能意味着您需要了解并喜欢 SQL。

I think this example shows very clearly the responsibility of SQL query generation when using Dapper, CA.Blocks.DataAccess or ADO.NET directly. When using these packages for accessing the database the developer is entirely in charge of the SQL query, its projection and execution. When using EF the responsibility of generating the query is removed from the developer and delegated to EF. This is a double-edged sword and can result in good queries as well as very bad queries. Most of the performance gains made in Dapper are from having full control over the SQL and eliminating bad SQL generation. The converse is also true, most of the performance problems with Dapper when compared to EF are due to EF creating a better query.
So what is happening here. In simple terms EF has looked at the request and has knowledge that you only what the first record FirstOrDefault so its query generation has resulted in

SELECT TOP 1 * FROM … WHERE…

The Dapper query you are making the comparison with is

SELECT * FROM … WHERE …

So the difference I suspect is purely on SQL. The Test database used, probably has many records in the Person table. Given the numbers it is likely that there is no index on name resulting in a Table Scan to find the matching data.

In the query generated by EF the database can stop the execute as soon as it finds the first record, in the Dapper example the database assembles the full record set with all the matches based on name then sends that row-set. Dapper is simply reading the first row and closing the connection.

To make this a fair comparison you need to change the query to be top 1. Like

 [Benchmark]
    public Player Dapper_GetByName()
    {
        using (SqlConnection conn = new(Database.ConnectionString))
        {
            return conn.QueryFirstOrDefault<Player>(
quot;SELECT Top 1 * FROM Players WHERE FirstName = @name", new 
            { @name = new DbString { Value = _name, IsAnsi = false } });
        }
    }

Also, the decision to go with Dapper for performance means you need to get to know and love SQL.

新一帅帅 2025-01-25 18:01:59

嗯,也许你不应该

// Open and Close a completely new database connection
using (SqlConnection conn = new(Database.ConnectionString))

与基准:

// Create a new Unit of Work / Transaction
using (ApplicationDbContext context = new())

只比较内部部分

return conn.QueryFirstOrDefault<Player>($"SELECT * FROM Players WHERE FirstName = '{_name}'");

Uhm, maybe you should not compare

// Open and Close a completely new database connection
using (SqlConnection conn = new(Database.ConnectionString))

vs

// Create a new Unit of Work / Transaction
using (ApplicationDbContext context = new())

Benchmark only the inner part:

return conn.QueryFirstOrDefault<Player>(
quot;SELECT * FROM Players WHERE FirstName = '{_name}'");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文