Dapper 使用教程
目录
- 什么是 Dapper?
- 为什么选择 Dapper?
- 环境准备
- 核心概念:
IDbConnection - 基础 CRUD 操作
- 查询数据
- 执行命令 (增、删、改)
- 事务处理
- 进阶用法
- 查询并映射到多个对象 (One-to-Many)
- 查询并映射到多个对象 (Many-to-Many)
- 动态查询
- 多结果集查询
- 参数化查询
- 最佳实践
- 完整示例项目
什么是 Dapper?
Dapper 是一个轻量级、高性能的 .NET 对象映射器,它由 Stack Overflow 团队开发并开源,主要用途是让你能够用最少的代码将数据库查询结果(如 DataTable 或 DataReader)映射到 .NET 对象(POCO/Model)。
你可以把它理解为 ADO.NET 的一个“超集”或“微扩展”,它不替代 ADO.NET,而是在 ADO.NET 的基础上提供了一层简洁的封装,让你写 SQL 的同时,能享受 ORM 带来的便利。
核心思想: "Write SQL, not code." (写 SQL,而不是写代码)。
为什么选择 Dapper?
- 高性能:Dapper 非常快,性能接近于原生 ADO.NET,因为它只是简单地反射了对象的属性并赋值,它的性能通常比 Entity Framework、NHibernate 等重量级 ORM 高出很多。
- 轻量级:Dapper 只是一个单独的 DLL 文件,没有复杂的配置和依赖,对项目侵入性极小。
- 简单易学:如果你会写 SQL,你几乎不需要学习任何新东西就能上手 Dapper,API 设计非常直观。
- 灵活可控:你可以自由地编写任何你想要的 SQL 查询,包括复杂的存储过程、多表连接查询等,不像某些 ORM 那样会生成难以优化的 SQL。
- 无状态:Dapper 本身是无状态的,它不维护对象上下文或会话,这让你可以完全控制数据库连接的生命周期。
环境准备
-
安装 NuGet 包: 在你的 .NET 项目(如 .NET Core / .NET 5/6/7/8)中,通过 NuGet 包管理器控制台或 Visual Studio 的 NuGet 包管理器安装
Dapper。Install-Package Dapper
-
准备数据库: 我们以一个简单的
Users表为例。CREATE TABLE Users ( Id INT PRIMARY KEY IDENTITY(1,1), Name NVARCHAR(100) NOT NULL, Email NVARCHAR(255) NOT NULL, Age INT, CreatedAt DATETIME2 DEFAULT GETDATE() ); -- 插入一些测试数据 INSERT INTO Users (Name, Email, Age) VALUES ('Alice', 'alice@example.com', 30), ('Bob', 'bob@example.com', 25), ('Charlie', 'charlie@example.com', 35);
核心概念:IDbConnection
Dapper 的所有扩展方法都是作为 System.Data.IDbConnection 接口的扩展方法存在的,这意味着任何实现了该接口的数据库连接类都可以使用 Dapper。
- SQL Server:
System.Data.SqlClient.SqlConnection - MySQL:
MySql.Data.MySqlClient.MySqlConnection - SQLite:
Microsoft.Data.Sqlite.SqliteConnection - PostgreSQL:
Npgsql.NpgsqlConnection
使用 Dapper 的第一步就是创建并打开一个 IDbConnection 对象。
using System.Data.SqlClient;
using Dapper;
// 创建连接字符串 (建议从配置文件中读取)
string connectionString = "Server=your_server;Database=your_db;User Id=your_user;Password=your_password;";
// 创建连接对象
using (IDbConnection db = new SqlConnection(connectionString))
{
// 打开连接 (Dapper 的大多数方法在执行前会自动检查连接状态,如果未打开会自动打开,但显式打开是好习惯)
db.Open();
// 在这里执行 Dapper 操作...
// using 语句会确保连接在代码块结束时被正确关闭和释放
}
基础 CRUD 操作
我们定义一个与 Users 表对应的 C# 模型类。
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int? Age { get; set; } // 使用可空类型对应数据库中的 NULL
public DateTime CreatedAt { get; set; }
}
1 查询数据
Dapper 提供了多种查询方法,最常用的是 Query<T>()。
-
查询单条记录
// 假设 db 连接已打开 string sql = "SELECT * FROM Users WHERE Id = @Id"; // Query<T> 返回一个 IEnumerable<T> // 使用 FirstOrDefault() 来获取单个对象,如果不存在则返回 null var user = db.Query<User>(sql, new { Id = 1 }).FirstOrDefault(); Console.WriteLine($"User: {user.Name}, Email: {user.Email}");sql: 你的 SQL 查询语句。new { Id = 1 }: 这是一个匿名对象,Dapper 会将其属性名和值作为参数化查询的参数。@Id是 SQL 中的参数占位符。强烈推荐使用参数化查询,以防止 SQL 注入!
-
查询多条记录
string sql = "SELECT * FROM Users WHERE Age > @MinAge"; // Query<T> 直接返回一个 IEnumerable<T>,包含所有匹配的记录 var users = db.Query<User>(sql, new { MinAge = 28 }); foreach (var u in users) { Console.WriteLine($"User: {u.Name}, Age: {u.Age}"); }
2 执行命令 (增、删、改)
对于不返回数据的操作(如 INSERT, UPDATE, DELETE),使用 Execute() 方法,它返回一个 int 值,表示受影响的行数。
-
插入数据
string sql = "INSERT INTO Users (Name, Email, Age) VALUES (@Name, @Email, @Age)"; var newUserData = new { Name = "David", Email = "david@example.com", Age = 28 }; // Execute 返回受影响的行数 int affectedRows = db.Execute(sql, newUserData); Console.WriteLine($"{affectedRows} row(s) inserted."); -
更新数据
string sql = "UPDATE Users SET Name = @Name, Age = @Age WHERE Id = @Id"; var updateData = new { Id = 2, Name = "Bob Smith", Age = 26 }; int affectedRows = db.Execute(sql, updateData); Console.WriteLine($"{affectedRows} row(s) updated."); -
删除数据
string sql = "DELETE FROM Users WHERE Id = @Id"; int affectedRows = db.Execute(sql, new { Id = 3 }); Console.WriteLine($"{affectedRows} row(s) deleted.");
3 事务处理
Dapper 对事务的支持非常简单,你只需要在 IDbConnection 上创建一个事务对象,并将其传递给 Query 或 Execute 方法即可。
string insertSql = "INSERT INTO Users (Name, Email, Age) VALUES (@Name, @Email, @Age)";
string deleteSql = "DELETE FROM Users WHERE Id = @Id";
// 1. 创建一个连接
using (IDbConnection db = new SqlConnection(connectionString))
{
db.Open();
// 2. 开始一个事务
using (IDbTransaction transaction = db.BeginTransaction())
{
try
{
// 3. 在执行方法中传入 transaction 对象
db.Execute(insertSql, new { Name = "Eve", Email = "eve@example.com", Age = 40 }, transaction);
db.Execute(deleteSql, new { Id = 4 }, transaction); // 假设 Id=4 的用户不存在
// 4. 如果所有操作都成功,提交事务
transaction.Commit();
Console.WriteLine("Transaction committed successfully.");
}
catch (Exception ex)
{
// 5. 如果发生任何异常,回滚事务
transaction.Rollback();
Console.WriteLine($"Transaction failed and was rolled back. Error: {ex.Message}");
}
}
}
进阶用法
1 查询并映射到多个对象 (One-to-Many)
这是 Dapper 最强大的功能之一,假设我们有 Posts 和 Comments 表,一个帖子可以有多个评论。
场景:查询所有帖子及其对应的评论列表。
模型类:
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public List<Comment> Comments { get; set; } = new List<Comment>();
}
public class Comment
{
public int CommentId { get; set; }
public string Content { get; set; }
public int PostId { get; set; }
}
SQL 查询:
我们需要在 SQL 中使用 AS 关键字为结果集中的列指定别名,并确保列名与 C# 属性名匹配或通过 Dapper.SqlMapper.SetTypeMap 等方式配置,最简单的方式是让列名和属性名一致(或忽略大小写)。
SELECT
p.PostId, p.Title,
c.CommentId, c.Content, c.PostId AS PostId_Comment -- 避免列名冲突
FROM Posts p
LEFT JOIN Comments c ON p.PostId = c.PostId
ORDER BY p.PostId;
Dapper 代码:
string sql = @"
SELECT
p.PostId, p.Title,
c.CommentId, c.Content, c.PostId AS PostId_Comment
FROM Posts p
LEFT JOIN Comments c ON p.PostId = c.PostId
ORDER BY p.PostId;";
// 使用 Query<T> 的重载方法,传入一个 Action<IDataReader, T> 来处理多映射
var postMap = new Dictionary<int, Post>();
var posts = db.Query<Post, Comment, Post>(
sql,
(post, comment) =>
{
// Dapper 会为每一行数据调用这个委托
// 1. 检查是否已经处理过这个 Post
if (!postMap.TryGetValue(post.PostId, out var existingPost))
{
existingPost = post;
postMap.Add(post.PostId, existingPost);
}
// 2. comment 不为空,则添加到列表中
if (comment != null)
{
existingPost.Comments.Add(comment);
}
return existingPost;
},
splitOn: "CommentId" // 告诉 Dapper 从哪个列开始映射到下一个对象
).Distinct().ToList(); // 使用 Distinct 去重,因为一个帖子有 N 条评论,就会返回 N 行
foreach (var post in posts)
{
Console.WriteLine($"Post: {post.Title}");
foreach (var comment in post.Comments)
{
Console.WriteLine($" - Comment: {comment.Content}");
}
}
2 动态查询
Dapper 可以直接将查询结果映射到 dynamic 对象,这在处理不固定结构的表或临时查询时非常有用。
string sql = "SELECT Name, Age FROM Users WHERE Age > @MinAge";
// Query<dynamic> 返回一个 IEnumerable<dynamic>
var users = db.Query<dynamic>(sql, new { MinAge = 25 });
foreach (var user in users)
{
// 访问动态对象的属性
Console.WriteLine($"Dynamic User: {user.Name}, Age: {user.Age}");
}
3 多结果集查询
一个查询可以返回多个结果集,Dapper 可以轻松处理这种情况。
SQL:
SELECT * FROM Users WHERE Age > 30; -- 第一个结果集 SELECT COUNT(*) FROM Users; -- 第二个结果集
Dapper 代码:
string sql = "SELECT * FROM Users WHERE Age > 30; SELECT COUNT(*) FROM Users;";
// QueryMultiple 用于处理多结果集
using (var multi = db.QueryMultiple(sql))
{
// 读取第一个结果集,并映射到 User 列表
var olderUsers = multi.Read<User>().ToList();
// 读取第二个结果集,它是一个单行单列的值
var userCount = multi.ReadFirst<int>(); // ReadFirst 直接读取第一个结果的第一行第一列
Console.WriteLine("Users older than 30:");
foreach (var user in olderUsers)
{
Console.WriteLine($"- {user.Name}");
}
Console.WriteLine($"Total users in database: {userCount}");
}
最佳实践
- 使用
using语句管理连接:始终使用using语句来确保IDbConnection对象在使用后被正确关闭和释放,即使在发生异常时也是如此。 - 优先使用参数化查询:永远不要使用字符串拼接来构建 SQL,这极易导致 SQL 注入攻击,Dapper 的匿名对象完美支持参数化查询。
- 保持 SQL 的清晰和可维护性:虽然 Dapper 给了你写任意 SQL 的自由,但也要确保 SQL 本身是易于阅读和维护的,对于复杂的查询,考虑使用存储过程。
- 处理空值:当数据库中的列可能为
NULL时,在 C# 模型中使用可空类型(如int?,DateTime?)。 - 分离数据访问层:不要在你的 UI 层或业务逻辑层中直接写 Dapper 代码,创建一个仓储类或服务类来封装所有数据库操作,遵循关注点分离的原则。
// 示例:UserRepository.cs
public class UserRepository
{
private readonly string _connectionString;
public UserRepository(string connectionString)
{
_connectionString = connectionString;
}
public User GetUserById(int id)
{
using (IDbConnection db = new SqlConnection(_connectionString))
{
string sql = "SELECT * FROM Users WHERE Id = @Id";
return db.Query<User>(sql, new { Id = id }).FirstOrDefault();
}
}
// ... 其他方法
}
完整示例项目
这是一个控制台应用的完整示例,展示了所有 CRUD 操作。
// Program.cs
using System;
using System.Data.SqlClient;
using System.Linq;
using Dapper;
// 定义 User 模型
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int? Age { get; set; }
public DateTime CreatedAt { get; set; }
}
class Program
{
static void Main(string[] args)
{
// !!! 替换为你的实际连接字符串 !!!
string connectionString = "Server=localhost;Database=DapperDemo;Trusted_Connection=True;TrustServerCertificate=True;";
// 1. 创建用户
Console.WriteLine("Creating a new user...");
using (IDbConnection db = new SqlConnection(connectionString))
{
var newUser = new { Name = "Frank", Email = "frank@example.com", Age = 50 };
string insertSql = "INSERT INTO Users (Name, Email, Age) VALUES (@Name, @Email, @Age); SELECT CAST(SCOPE_IDENTITY() as int)";
int newUserId = db.Query<int>(insertSql, newUser).Single();
Console.WriteLine($"Created user with ID: {newUserId}");
}
// 2. 查询所有用户
Console.WriteLine("\nAll users:");
using (IDbConnection db = new SqlConnection(connectionString))
{
string sql = "SELECT * FROM Users";
var users = db.Query<User>(sql).ToList();
foreach (var user in users)
{
Console.WriteLine($"ID: {user.Id}, Name: {user.Name}, Email: {user.Email}, Age: {user.Age}");
}
}
// 3. 更新用户
Console.WriteLine("\nUpdating user with ID 2...");
using (IDbConnection db = new SqlConnection(connectionString))
{
var updateData = new { Id = 2, Name = "Bob Updated", Age = 27 };
string updateSql = "UPDATE Users SET Name = @Name, Age = @Age WHERE Id = @Id";
int affectedRows = db.Execute(updateSql, updateData);
Console.WriteLine($"{affectedRows} row(s) updated.");
}
// 4. 删除用户
Console.WriteLine("\nDeleting user with ID 5 (if exists)...");
using (IDbConnection db = new SqlConnection(connectionString))
{
string deleteSql = "DELETE FROM Users WHERE Id = @Id";
int affectedRows = db.Execute(deleteSql, new { Id = 5 });
Console.WriteLine($"{affectedRows} row(s) deleted.");
}
}
}
Dapper 是一个强大而简单的工具,它完美地平衡了性能和灵活性,当你需要:
- 极致的性能:特别是在读多写少的场景下。
- 完全的 SQL 控制:需要编写复杂的、特定于数据库的查询。
- 轻量级集成:不想被一个庞大的 ORM 框架所束缚。
Dapper 是你的不二之选,对于简单的 CRUD 应用,它几乎可以满足所有需求;对于大型应用,它可以作为 EF Core 等重量级 ORM 的一个有力补充,专门处理性能关键路径的代码。
希望这份教程能帮助你快速上手 Dapper!
