C#.NET dapper 详解

简介

Dapper 是由 Stack Overflow 团队开发的一个简单、高性能的微型 ORM(Object‑Relational Mapper),仅几千行代码,依赖于 ADO.NET 的 IDbConnection,通过动态生成 IL 来映射结果到实体对象。

与 EF、NHibernate 这类全功能 ORM 相比,Dapper 提供了更直接、更接近 SQL 的操作方式,性能非常接近手写 ADO.NET。

基本用法

安装与配置

shell 复制代码
dotnet add package Dapper
dotnet add package Dapper.Contrib
dotnet add package System.Data.SqlClient # SQL Server

打开连接

csharp 复制代码
using (var conn = new SqlConnection(connectionString))
{
    conn.Open();
    // ... 执行 Dapper 操作
}

查询单个实体

csharp 复制代码
string sql = "SELECT Id, Name, Age FROM Users WHERE Id = @Id";
var user = conn.QueryFirstOrDefault<User>(sql, new { Id = 123 });
  • QueryFirstOrDefault<T>:返回第一行并映射为 T,无数据时返回 default(T)。

  • 还有 QuerySingle<T>、Query<T>(返回 IEnumerable<T>)等方法。

查询多个对象

csharp 复制代码
using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    var users = connection.Query<User>(
        "SELECT * FROM Users WHERE Age > @Age", 
        new { Age = 18 });
    return users.ToList();
}

执行增删改

csharp 复制代码
string insert = "INSERT INTO Users (Name, Age) VALUES (@Name, @Age)";
int rows = conn.Execute(insert, new { Name = "Alice", Age = 28 });
  • Execute:用于执行不返回结果集的 SQL,返回受影响行数。

基础 CRUD 操作

csharp 复制代码
using var connection = new SqlConnection(connectionString);

// 查询
var product = connection.QueryFirstOrDefault<Product>(
    "SELECT * FROM Products WHERE Id = @Id", 
    new { Id = 1 });

// 插入
var newProduct = new Product { Name = "Laptop", Price = 999.99m };
var insertedId = connection.ExecuteScalar<int>(
    "INSERT INTO Products (Name, Price) OUTPUT INSERTED.Id VALUES (@Name, @Price)",
    newProduct);

// 更新
var rowsAffected = connection.Execute(
    "UPDATE Products SET Price = @Price WHERE Id = @Id",
    new { Id = 1, Price = 899.99m });

// 删除
connection.Execute(
    "DELETE FROM Products WHERE Id = @Id", 
    new { Id = 10 });

关键方法详解

方法 描述 示例
Query<T> 返回实体列表 connection.Query<Product>("SELECT * FROM Products")
QueryFirst<T> 返回第一条结果(无结果抛异常) connection.QueryFirst<Product>("SELECT ... WHERE Id=@id", new {id=1})
QueryFirstOrDefault<T> 返回第一条或默认值(无结果返回 null) connection.QueryFirstOrDefault<Product>(...)
QuerySingle<T> 返回单条结果(结果不唯一抛异常) 适用于主键查询
Execute 执行非查询操作(增删改),返回受影响行数 connection.Execute("DELETE FROM Products WHERE Id=@id", new {id=10})
ExecuteScalar<T> 返回单个值(如 COUNT、SUM) int count = connection.ExecuteScalar<int>("SELECT COUNT(*) FROM Products")

高级查询技术

多结果集处理

csharp 复制代码
using var multi = connection.QueryMultiple(
    "SELECT * FROM Products; SELECT * FROM Categories");

var products = multi.Read<Product>();
var categories = multi.Read<Category>();

多表关联映射(Multi‑Mapping)

当一个查询返回多张表的数据,需要映射到不同实体并组合起来时:

csharp 复制代码
string sql = @"
SELECT o.Id, o.OrderDate,
       c.Id, c.Name 
FROM Orders o
JOIN Customers c ON o.CustomerId = c.Id";

var list = conn.Query<Order, Customer, Order>(
    sql,
    (order, cust) => { order.Customer = cust; return order; },
    splitOn: "Id");
  • splitOn 指定从哪个列开始分割下一张表的映射。

一对多关系映射

csharp 复制代码
var sql = @"
    SELECT o.*, i.* 
    FROM Orders o
    INNER JOIN OrderItems i ON o.Id = i.OrderId
    WHERE o.CustomerId = @CustomerId";

var orderDictionary = new Dictionary<int, Order>();

var orders = connection.Query<Order, OrderItem, Order>(
    sql,
    (order, item) =>
    {
        if (!orderDictionary.TryGetValue(order.Id, out var existingOrder))
        {
            existingOrder = order;
            existingOrder.Items = new List<OrderItem>();
            orderDictionary.Add(existingOrder.Id, existingOrder);
        }
        existingOrder.Items.Add(item);
        return existingOrder;
    },
    new { CustomerId = 123 },
    splitOn: "Id");

Dapper.Contrib 扩展

自动 CRUD 操作

csharp 复制代码
// 实体类标记
[Table("Products")]
public class Product
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// 自动操作
var product = connection.Get<Product>(1);          // 查询
connection.Insert(new Product { ... });           // 插入
connection.Update(product);                       // 更新
connection.Delete(product);                       // 删除

// 批量操作
connection.InsertAll(products);                   // 批量插入

自定义映射规则

csharp 复制代码
SqlMapper.SetTypeMap(
    typeof(Product),
    new CustomPropertyTypeMap(
        typeof(Product),
        (type, columnName) => 
            type.GetProperties().FirstOrDefault(prop => 
                prop.GetCustomAttributes(false)
                    .OfType<ColumnAttribute>()
                    .Any(attr => attr.Name == columnName))));

性能优化技巧

参数化查询最佳实践

csharp 复制代码
// 正确:参数化防止SQL注入
connection.Query("SELECT * FROM Users WHERE Name = @Name", new { Name = "Alice" });

// 错误:拼接SQL风险
connection.Query($"SELECT * FROM Users WHERE Name = '{"Alice"}'");

批处理与事务

csharp 复制代码
using var transaction = connection.BeginTransaction();

try
{
    // 批量插入
    connection.Execute(
        "INSERT INTO Logs (Message) VALUES (@Message)",
        logs.Select(l => new { l.Message }),
        transaction);
    
    // 批量更新
    connection.Execute(
        "UPDATE Products SET Stock = Stock - @Quantity WHERE Id = @Id",
        orderItems.Select(i => new { i.Quantity, i.ProductId }),
        transaction);
    
    transaction.Commit();
}
catch
{
    transaction.Rollback();
    throw;
}

异步操作支持

csharp 复制代码
public async Task<Product> GetProductAsync(int id)
{
    using var conn = new SqlConnection(connectionString);
    return await conn.QueryFirstOrDefaultAsync<Product>(
        "SELECT * FROM Products WHERE Id = @Id", 
        new { Id = id });
}

高级应用场景

动态查询构建

csharp 复制代码
public IEnumerable<Product> SearchProducts(ProductSearchCriteria criteria)
{
    var sql = new StringBuilder("SELECT * FROM Products WHERE 1=1");
    var parameters = new DynamicParameters();
    
    if (!string.IsNullOrEmpty(criteria.Name))
    {
        sql.Append(" AND Name LIKE @Name");
        parameters.Add("Name", $"%{criteria.Name}%");
    }
    
    if (criteria.MinPrice.HasValue)
    {
        sql.Append(" AND Price >= @MinPrice");
        parameters.Add("MinPrice", criteria.MinPrice);
    }
    
    return connection.Query<Product>(sql.ToString(), parameters);
}

JSON 数据处理(SQL Server 2016+)

csharp 复制代码
// 查询JSON列
var orders = connection.Query<Order>(@"
    SELECT 
        Id,
        JSON_VALUE(Details, '$.Customer.Name') AS CustomerName,
        JSON_VALUE(Details, '$.TotalAmount') AS TotalAmount
    FROM Orders");

// 插入JSON数据
connection.Execute(
    "INSERT INTO Orders (Details) VALUES (@Details)",
    new { Details = JsonConvert.SerializeObject(orderDetails) });

参数化查询

Dapper 默认将匿名对象的属性映射到 SQL 中的参数,杜绝 SQL 注入:

csharp 复制代码
var parameters = new DynamicParameters();
parameters.Add("MinPrice", 100);
parameters.Add("MaxPrice", 500);
var products = conn.Query<Product>(
    "SELECT * FROM Products WHERE Price BETWEEN @MinPrice AND @MaxPrice",
    parameters);
  • DynamicParameters 支持输出参数、存储过程参数等高级场景。

最佳实践指南

与 EF Core 混合使用

csharp 复制代码
// 复杂查询用 Dapper
public List<ReportItem> GetSalesReport(DateTime start, DateTime end)
{
    return _dapperConnection.Query<ReportItem>(...);
}

// 事务性操作用 EF Core
public void PlaceOrder(Order order)
{
    using var transaction = _dbContext.Database.BeginTransaction();
    try
    {
        _dbContext.Orders.Add(order);
        _dbContext.SaveChanges();
        
        // 调用库存服务(Dapper)
        _dapperConnection.Execute(...);
        
        transaction.Commit();
    }
    catch
    {
        transaction.Rollback();
        throw;
    }
}

性能关键路径优化

csharp 复制代码
// 缓存查询结果(高频读取)
private static readonly string ProductByIdSql = 
    "SELECT * FROM Products WHERE Id = @Id";

public Product GetProduct(int id)
{
    return _connection.QueryFirstOrDefault<Product>(
        ProductByIdSql, new { Id = id });
}

// 使用存储过程
var products = _connection.Query<Product>(
    "usp_GetProductsByCategory", 
    new { CategoryId = 5 }, 
    commandType: CommandType.StoredProcedure);

Dapper 扩展生态

常用扩展库

库名 功能 安装命令
Dapper.Contrib 基础CRUD扩展 Install-Package Dapper.Contrib
Dapper.FluentMap 高级映射配置 Install-Package Dapper.FluentMap
Dapper.SimpleCRUD 自动化CRUD操作 Install-Package Dapper.SimpleCRUD
Dapper.SqlBuilder 动态SQL构建 Install-Package Dapper.SqlBuilder

数据库提供程序支持

csharp 复制代码
// PostgreSQL
using var conn = new NpgsqlConnection(pgConnectionString);

// MySQL
using var conn = new MySqlConnection(mysqlConnectionString);

// SQLite
using var conn = new SQLiteConnection(sqlliteConnectionString);

Dapper 适用场景分析

推荐使用场景

  • 高性能报表查询:大数据量复杂查询

  • 批量数据处理:ETL 管道操作

  • 微服务架构:轻量级数据访问层

  • 遗留系统集成:替代原生 ADO.NET

  • 读写分离架构:读操作专用

不推荐场景

  • 复杂领域模型:需要变更跟踪

  • 数据库迁移需求:缺乏迁移工具

  • 自动 SQL 生成:需手写 SQL

  • 简单 CRUD 应用:EF Core 更高效

总结:Dapper 核心价值

  • 极致性能:接近原生 ADO.NET 的执行效率

  • 精细控制:完全掌控 SQL 语句

  • 轻量灵活:最小化 ORM 开销

  • 易于集成:与现有项目无缝结合

  • 丰富扩展:强大的社区生态支持

存储过程调用

csharp 复制代码
var users = conn.Query<User>(
    "sp_GetActiveUsers",
    new { IsActive = true },
    commandType: CommandType.StoredProcedure);
  • 也可用 Execute、QueryMultiple(一次取多组结果)等。

性能优势

  • 由于 Dapper 直接在运行时生成 IL 来做对象映射,并且复用 ADO.NET 连接与命令,对大量并发、批量查询场景性能非常优秀。

  • 对比 EF Core,Dapper 的查询速度常常有 2--10 倍优势,尤其是返回扁平对象时。

与 EF 的对比

Dapper EF Core
编写 SQL 手写、完全自定义 LINQ 语句
配置映射 自动映射或手动配置(少量) 属性与 Fluent API
性能 极高,接近原生 ADO.NET 较高,但 LINQ 翻译与变更追踪有额外开销
灵活度 完全掌控 SQL 高,受限于 LINQ 表达式能力
学习成本 低 相对高(迁移、关系、导航属性等)

常见问题与最佳实践

  • 连接管理:建议每次操作单独 using 打开连接,避免长连接导致资源占用。

  • 参数重用:对于频繁相同参数的查询,可重用 DynamicParameters 对象,提升性能。

  • 大型结果集:如果结果集很大,使用 Query<T> 时可结合 AsList() 分批处理,或使用 IDataReader 手动读取。

  • 分页:可在 SQL 中直接使用 OFFSET...FETCH,或结合动态参数快速实现。

  • 缓存:Dapper 自带简单的 SQL 缓存(IL 生成后会缓存映射),但不缓存查询结果;如需缓存结果,可加分布式/内存缓存。

相关推荐
玩泥巴的1 小时前
源生成与 AOT 就绪的微信生态 .NET SDK:结构、能力边界与 API 定位方法
ai·微信·.net·agent·aot·roslyn
氷凌公子2 小时前
算法与UI拆分中遇到的问题及解决方式
c#·onnx·yolov26
1-1=02 小时前
C# 泛型
c#·.net·.netcore
有志冬瓜2 小时前
.NET Framework全部版本离线下载安装教程
安全·.net·软件需求
IT规划师3 小时前
Redis 数据类型全解 - Set 的三个真相:去重、标签、共同好友(四)
redis·.net
椒颜皮皮虾྅1 天前
让文档不止于文字:DeploySharp + PP-Structure,打通版面分析与表格重建
开发语言·学习·c#·.net·cuda
玩泥巴的1 天前
可能是最现代化的微信生态 .NET 服务端 SDK
ai·微信·.net·aot
椒颜皮皮虾྅1 天前
一套 C# 接口,解锁四类视觉任务:DeploySharp 分类、分割、关键点与旋转框实战
开发语言·分类·c#
罗迪尼亚的熔岩1 天前
联合kuka机器人, 海康visonmaster 的手眼9点标定方法
机器人·c#·工业相机·标定