Skip to content
Go back

EF Core vs Dapper:.NET 数据访问怎么选

选数据访问库是那种静悄悄影响后续一切的决定。EF Core vs Dapper 的争论几乎出现在每个 .NET 项目里,两边都有热情的支持者。现实是:没有哪个工具绝对更好。它们在不同抽象层级解决不同问题——知道你的问题到底是什么,才是选对工具的关键。

两个工具分别是什么

EF Core 是 Microsoft 构建的完整 ORM。它提供 code-first 建模、数据库迁移、LINQ 到 SQL 翻译、变更追踪和 scaffolding。设计目标是开发者生产力——让你用 C# 思考领域模型而不是写 SQL。

在 EF Core 10 中,预编译查询(AOT)已生产可用,LINQ 翻译持续改进,SQLite 日期时间处理也更完善。JSON 列支持和复杂类型(EF Core 8)仍是关键特性。

Dapper 是 Stack Overflow 团队创建的 micro-ORM。它只做一件事:把 SQL 查询结果映射到 C# 对象。没有 LINQ 翻译、没有迁移、没有变更追踪、没有代码生成。你写 SQL,Dapper 跑 SQL,你拿到对象。整个库就是 IDbConnection 上的一层薄扩展。

逐项对比

维度EF CoreDapper
抽象层级高(C# 描述意图,EF 生成 SQL)低(自己写 SQL,Dapper 管映射)
原始性能良好(AsNoTracking + 编译查询后接近 Dapper)极佳(几乎无 ADO.NET 之上的额外开销)
查询控制高(LINQ 覆盖 90%,FromSqlRaw 兜底)完全控制(你写的 SQL 就是最终执行的 SQL)
数据库迁移内置(dotnet ef migrations add无(需配合 FluentMigrator / DbUp / Liquibase)
学习曲线中等(需理解 DbContext 生命周期、变更追踪、LINQ 翻译陷阱)低(会 SQL 就会 Dapper)
团队适配适合 C# 思维、对象优先的团队适合 SQL 思维、查询优先的团队
复杂 Schema优秀(多对多、继承映射、owned types、temporal tables)有限(多结果集、复杂 join 需手动处理)

同一个查询,两种写法

以”按标签查询已发布的博客文章及其作者”为例:

// EF Core:LINQ + Include + AsNoTracking
public async Task<IReadOnlyList<BlogPostSummary>> GetPublishedByTagAsync(
    string tag, CancellationToken ct = default)
{
    return await db.BlogPosts
        .AsNoTracking()
        .Where(p => p.IsPublished && p.Tags.Contains(tag))
        .OrderByDescending(p => p.PublishedAt)
        .Select(p => new BlogPostSummary(
            p.Id, p.Title, p.Author.DisplayName, p.PublishedAt))
        .ToListAsync(ct);
}

// Dapper:手写 SQL,精确控制
const string sql = """
    SELECT p.Id, p.Title, a.DisplayName AS AuthorName, p.PublishedAt
    FROM BlogPosts p
    INNER JOIN Authors a ON a.Id = p.AuthorId
    WHERE p.IsPublished = 1 AND p.Tags LIKE @TagPattern
    ORDER BY p.PublishedAt DESC
    """;

var results = await db.QueryAsync<BlogPostSummary>(
    new CommandDefinition(sql, new { TagPattern = $"%{tag}%" },
        cancellationToken: ct));

EF Core 版本更重构安全(重命名 C# 属性不会静默破坏查询),Dapper 版本让你精确控制 SQL 且没有意外。

写入侧也是同样模式——EF Core 通过变更追踪自动生成 INSERT,Dapper 需要手写 INSERT 语句。

混合使用:两者放一个项目里

这是成熟 .NET 应用最务实的答案。不需要二选一然后到处用同一个。

常见模式:EF Core 管所有写入和复杂领域操作(变更追踪和迁移的价值在这里),Dapper 管读取投射和报表查询(原始性能和控制在这里)。

public sealed class OrderService(AppDbContext db)
{
    // EF Core 管领域写入
    public async Task PlaceOrderAsync(PlaceOrderRequest request,
        CancellationToken ct = default)
    {
        var order = new Order { ... };
        db.Orders.Add(order);
        await db.SaveChangesAsync(ct);
    }

    // Dapper 管读取投射,复用同一个连接
    public async Task<IReadOnlyList<OrderSummaryDto>>
        GetCustomerOrderSummaryAsync(int customerId,
            CancellationToken ct = default)
    {
        var connection = db.Database.GetDbConnection();
        const string sql = """
            SELECT o.Id, o.PlacedAt, o.Status,
                   SUM(l.Quantity * l.UnitPrice) AS TotalAmount
            FROM Orders o
            INNER JOIN OrderLines l ON l.OrderId = o.Id
            WHERE o.CustomerId = @CustomerId
            GROUP BY o.Id, o.PlacedAt, o.Status
            """;
        var results = await connection.QueryAsync<OrderSummaryDto>(
            sql, new { CustomerId = customerId });
        return results.AsList();
    }
}

db.Database.GetDbConnection() 让 Dapper 复用 EF Core 管理的数据库连接。如果在事务作用域内,Dapper 也可以通过 db.Database.CurrentTransaction?.GetDbTransaction() 参与同一个事务。

决策矩阵

场景推荐理由
标准 CRUD 应用EF Core开发效率、迁移、变更追踪物有所值
极端性能要求Dapper最小开销,完全 SQL 控制
简单脚本或 ETLDapper不需要完整 ORM
快速原型、约定驱动EF CoreScaffolding + 迁移 + LINQ 大幅减样板
遗留库 + 存储过程Dapper 或混合存储过程结果自然地映射到 Dapper
复杂领域模型EF CoreOwned types、继承、导航属性是 EF 强项
读密集型报表查询Dapper手写 SQL,无变更追踪开销
新项目、团队会 C# 不会 SQLEF CoreLINQ 覆盖多数场景,减少上下文切换

关于性能数据

你在网上看到的 benchmark——“Dapper 比 EF Core 快 2 倍”或者”带编译查询的 EF Core 只慢 5% “——除非跟你的场景匹配,否则都该持怀疑态度。

性能高度依赖:(1) 查询复杂度——简单 SELECT * WHERE Id = @Id 会放大 Dapper 优势,带 join 的复杂聚合是另一回事;(2) 连接池配置——对短查询来说,连接开/关开销可能比 ORM 开销大得多;(3) 变更追踪范围——读路径加 AsNoTracking() 可以消掉大部分差距;(4) 编译查询——EF Core 编译查询缓存 LINQ 翻译结果,能让热路径性能非常接近 Dapper。

正确做法:用 BenchmarkDotNet,在近似生产的硬件上、针对你的实际数据库和实际查询做 benchmark。

结论

选 EF Core,当你:在构建标准 .NET 应用且开发者效率是关键,需要框架管理 schema 迁移,团队习惯 C# 和对象思维多于 SQL,以及处理复杂领域模型。

选 Dapper,当你:团队 SQL 熟练,处理遗留数据库或大量存储过程,需要报表查询的原始性能,或构建简单数据访问脚本不需要完整 ORM。

两个都用,当你:应用有分明的读写模式——EF Core 管领域写入侧(变更追踪和迁移有回报),Dapper 管读取投射侧(要完全 SQL 控制和最小开销)。这不是妥协,而是很多成熟 .NET 应用在生产中使用的架构决策。

两个工具都没错。错的是教条式地选一个工具然后把它强塞进所有场景——包括另一个工具显然更适合的那些。

如果你关注 AI 助手、开发工具和软件工程实践,可以关注 Aide Hub。这里会继续分享能落地的工具教程、技术观察和项目经验。

参考


Tags


Next

一位资深 .NET 开发者的 20+ 条实战建议:从 DI、Async 到 EF Core 优化