C#.NET dapper 详解

简介

Dapper 是由 Stack Overflow 团队开发的一个简单、高性能的微型 ORM(Object‑Relational Mapper),仅几千行代码,依赖于 ADO.NETIDbConnection,通过动态生成 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 CoreDapper 的查询速度常常有 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 生成后会缓存映射),但不缓存查询结果;如需缓存结果,可加分布式/内存缓存。

相关推荐
唐青枫8 小时前
C#.NET SqlKata 使用详解:优雅构建动态 SQL 查询
c#·.net
虚行16 小时前
C#上位机工程师技能清单文档
开发语言·c#
追逐时光者17 小时前
一个基于 .NET 开源、功能强大的分布式微服务开发框架
后端·.net
笺上知微17 小时前
Serilog基于Seq开源框架实现日志分析
.net
百锦再17 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
CodeCraft Studio18 小时前
【能源与流程工业案例】KBC借助TeeChart 打造工业级数据可视化平台
java·信息可视化·.net·能源·teechart·工业可视化·工业图表
一个帅气昵称啊19 小时前
使用微软Agent Framework .NET构建智能代理应用
microsoft·flask·.net
小白杨树树19 小时前
【C++】力扣hot100错误总结
c++·leetcode·c#
Tiger_shl20 小时前
三大并发集合ConcurrentDictionary、ConcurrentBag、ConcurrentQueue
开发语言·c#
一个天蝎座 白勺 程序猿20 小时前
深度解析:通过ADO.NET驱动Kdbndp高效连接与操作Kingbase数据库
数据库·.net·wpf·kingbase·金仓数据库