详情

首页手游攻略 C#常用类库Dapper完整指南

C#常用类库Dapper完整指南

佚名 2026-09-09 20:40:02

平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“C#常用类库Dapper”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。

目录
  • 一、前言:Dapper的定位与核心优势
    • 1. 核心定位
    • 2. 核心优势(对比EF Core)
    • 3. 版本与安装
  • 二、基础用法:从零开始采用Dapper(核心CRUD)
    • 1. 准备工作:实体类与数据库
    • 2. 核心基础:新建数据库连接
    • 3. 核心CRUD操作(重点)
      • (1)新增(Insert)
      • (2)查询(Select)
      • (3)修改(Update)
      • (4)删除(Delete)
    • 4. 核心API总结(必记)
    • 三、核心特性:Dapper进阶用法(深度重点)
      • 1. 参数化查询详解(防SQL注入,必掌握)
        • (1)匿名对象参数(最常用)
        • (2)实体类参数(适用来参数较多的场景)
        • (3)DynamicParameters参数(适用来动态参数、多参数场景)
      • 2. 多结果集查询(QueryMultiple)
        • 3. 联表查询与实体映射(复杂场景)
          • (1)轻松联表(自动映射)
          • (2)复杂联表(手动映射)
        • 4. 存储过程调用(企业级项目常用)
          • 示例:调用存储过程查询用户
          • 示例:调用带输出参数的存储过程
        • 5. 事务处理(保证数据一致性)
          • (1)原生事务(IDbTransaction)
          • (2)分布式事务(TransactionScope)
      • 四、高级技巧:Dapper扩展与最佳实践
        • 1. Dapper扩展包建议(必装)
          • (1)Dapper.Contrib(简化CRUD,无需写SQL)
          • (2)Dapper.SqlBuilder(动态构建SQL)
        • 2. 项目中Dapper的最佳实践
          • (1)封装Dapper工具类(全局单例)
          • (2)分层架构中采用Dapper(DAL层封装)
          • (3)SQL语句优化(提升性能)
      • 五、性能优化:让Dapper跑得更快(核心重点)
        • 1. 连接管理优化(最关键)
          • 2. 映射优化(减少反射开销)
            • 3. SQL优化(核心中的核心)
              • 4. 高频场景优化示例
              • 六、避坑指南:常用问题与解决方案
                • 1. 问题1:参数名与实体类属性名不一致,映射失败
                  • 2. 问题2:自增主键得到失败(SQL Server)
                    • 3. 问题3:多结果集读取顺序错误,导致映射失败
                      • 4. 问题4:事务提交后,数据未生效
                        • 5. 问题5:高频调用时性能下降
                        • 七、总结

                          理解这一步时,作为C#开发者,在数据访问层(DAL)的开发中,我们始终面临一个选择:是采用EF Core这类“大而全”的ORM框架,还是选择轻量级、高性能的工具?对于追求极致性能、需灵活控制SQL、厌恶“黑箱操作”的场景,Dapper无疑是最优解。

                          落到代码里,Dapper是由Stack Overflow团队开发的轻量级ORM工具,它同时非替代EF Core,而是以“简洁、高效、灵活”为核心,在保持原生SQL控制力的同时,简化数据映射的繁琐操作,完美平衡了“性能”与“开发效率”。本文将从基础用法→核心特性→高级技巧→性能优化→避坑指南实际处理时,,全方位、有深度地解析Dapper,结合实际项目场景,帮你彻底掌握这款“性能王者”级别的数据访问工具。

                          一、前言:Dapper的定位与核心优势

                          从实现思路看,在正式讲解用法前,我们先明确Dapper的核心定位、优势,以及它与EF Core的差异——这是实际项目中框架选型的关键,也是理解Dapper设计理念的基础。

                          1. 核心定位

                          Dapper是一款轻量级对象关系映射(ORM)工具,核心目标是“简化数据映射,不牺牲性能与灵活性从实现思路看,”。它本质上是对ADO.NET的封装,屏蔽了ADO.NET中繁琐的连接、命令、数据读取操作,同时保留了原生SQL的全部控制力,避免了重量级ORM(如EF Core)的性能开销和“黑箱”问题。

                          从实现思路看,Dapper兼容所有主流关系型数据库(SQL Server、MySQL、Oracle、PostgreSQL等),兼容.NET Framework 4.5+、.NET Core 2.0+、.NET 5/6/7/8等所有主流.NET平台,是分层架构、微服务、高性能系统中数据访问层的首选工具。

                          2. 核心优势(对比EF Core)

                          落到代码里,Dapper的核心竞争力在于“轻量、高效、灵活”,与EF Core相比,优势和适用场景差异明显,具体对比如下所示:

                          对比维度DapperEF Core
                          性能极高,接近原生ADO.NET,反射开销极低中等,存在LINQ解析、跟踪代理等性能开销
                          SQL控制力完全可控,需手动编写SQL,兼容复杂查询、存储过程自动生成SQL,复杂查询需写原生SQL或复杂LINQ,灵活性较弱
                          学习成本低,API简洁,核心方法仅10+个,依赖SQL基础高,需掌握LINQ、EF Core映射规则、迁移、跟踪机制等
                          适用场景高性能需求、复杂SQL查询、存储过程、老项目改造、微服务更快开发、轻松CRUD、需ORM抽象、团队熟悉LINQ
                          侵入性无侵入,实体类无需继承任何接口、标注任何特性有侵入,实体类需标注特性(如[Key]),依赖EF Core核心包
                          补充:选型建议——小型项目、更快开发选EF Core;高性能、复杂SQL、微服务选Dapper;实际项目中也可混合采用(如核心查询用Dapper,轻松CRUD用EF Core)。

                          3. 版本与安装

                          在这个场景下,Dapper最新稳定版本为2.0+,安装方式极其轻松,借助NuGet安装核心包即可,根据数据库类型选择对应扩展包:

                          • 核心包(必装):Install-Package Dapper(.NET CLI:dotnet add package Dapper
                          • 数据库扩展包(按需安装):
                            • SQL Server:无需额外扩展(依赖System.Data.SqlClient)
                            • MySQL:Install-Package MySqlConnector(建议,替代旧版MySql.Data)
                            • Oracle:Install-Package Oracle.ManagedDataAccess.Core

                          理解这一步时,安装完成后,引入核心命名空间即可采用:using Dapper;

                          二、基础用法:从零开始采用Dapper(核心CRUD)

                          Dapper的核心是扩展方法——它借助扩展IDbConnection接口,提供了一系列简洁的方法(如Query、Execute),无需新建复杂对象,即可完成数据访问操作。基础用法围绕“连接字符串→新建连接→执行SQL→映射结果”展开,以下是完整示例。

                          1. 准备工作:实体类与数据库

                          从实现思路看,以SQL Server为例,新建数据库表和对应实体类(Dapper无需实体类与表严格对应,灵活度极高):

                          -- 创建用户表
                          CREATE TABLE [dbo].[Users] (
                              [Id] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
                              [UserName] NVARCHAR(50) NOT NULL,
                              [Age] INT NOT NULL,
                              [Email] NVARCHAR(100) NULL,
                              [CreateTime] DATETIME NOT NULL DEFAULT GETDATE()
                          )

                          // 实体类(无侵入,无需继承任何接口、标注特性)
                          public class User
                          {
                              public int Id { get; set; }
                              public string UserName { get; set; }
                              public int Age { get; set; }
                              public string Email { get; set; }
                              public DateTime CreateTime { get; set; }
                          }

                          2. 核心基础:新建数据库连接

                          Dapper依赖IDbConnection接口,需先新建数据库连接(建议采用using语句自动释放连接,避免连接泄漏):

                          using System.Data;
                          using System.Data.SqlClient;
                          using Dapper;
                          // 1. 连接字符串(建议放在配置文件中,此处简化)
                          string connectionString = "Data Source=.;Initial Catalog=DapperDemo;User ID=sa;Password=123456;";
                          // 2. 使用using语句创建连接(自动打开、关闭连接)
                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              // 打开连接(Dapper的部分方法会自动打开连接,建议手动打开,提升性能)
                              conn.Open();
                              // 执行SQL操作(后续讲解)
                          }

                          【关键提醒】:using语句会自动释放连接,无需手动调用conn.Close()conn.Dispose();手动打开连接(conn.Open())可避免Dapper自动打开/关闭连接的额外开销,尤其适合批量操作。

                          3. 核心CRUD操作(重点)

                          Dapper的核心方法集中在IDbConnection的扩展方法中,最常用的有Query(查询)、Execute(执行增删改)、QueryFirstOrDefault(查询单个结果)等,以下是完整CRUD示例。

                          (1)新增(Insert)

                          采用Execute方法执行插入SQL,兼容参数化查询(防止SQL注入,必用):

                          // 准备新增数据
                          var newUser = new User
                          {
                              UserName = "张三",
                              Age = 25,
                              Email = "[email protected]",
                              CreateTime = DateTime.Now
                          };
                          // 插入SQL(参数化,@UserName对应实体类的属性名)
                          string insertSql = @"INSERT INTO Users (UserName, Age, Email, CreateTime)
                                              VALUES (@UserName, @Age, @Email, @CreateTime);
                                              SELECT SCOPE_IDENTITY();"; // 返回自增ID
                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              conn.Open();
                              // 执行插入,返回自增ID(Convert.ToInt32转换,适配SQL Server)
                              int newUserId = conn.ExecuteScalar<int>(insertSql, newUser);
                              Console.WriteLine($"新增用户成功,ID:{newUserId}");
                          }

                          关键说明:

                          • ExecuteScalar<T>:执行SQL同时得到单个结果(如自增ID),泛型T指定得到值类型。
                          • 参数化查询:Dapper会自动将实体类的属性与SQL中的@参数名对应,无需手动拼接SQL,彻底避免SQL注入。

                          (2)查询(Select)

                          在这个场景下,查询是Dapper最常用的操作,核心方法是Query<T>(查询多个结果)和QueryFirstOrDefault<T>(查询单个结果),兼容复杂SQL、联表查询。

                          // 示例1:查询所有用户(返回List<User>)
                          string selectAllSql = "SELECT * FROM Users";
                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              conn.Open();
                              // Query<T>返回IEnumerable<T>,可直接转换为List
                              List<User> userList = conn.Query<User>(selectAllSql).ToList();
                              foreach (var user in userList)
                              {
                                  Console.WriteLine($"ID:{user.Id},姓名:{user.UserName}");
                              }
                          }
                          // 示例2:根据ID查询单个用户(查询不到返回null)
                          int userId = 1;
                          string selectOneSql = "SELECT * FROM Users WHERE Id = @Id";
                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              conn.Open();
                              // QueryFirstOrDefault<T>:查询第一个结果,无结果返回null
                              User user = conn.QueryFirstOrDefault<User>(selectOneSql, new { Id = userId });
                              if (user != null)
                              {
                                  Console.WriteLine($"查询到用户:{user.UserName}");
                              }
                              else
                              {
                                  Console.WriteLine("未查询到用户");
                              }
                          }
                          // 示例3:条件查询(多参数)
                          int minAge = 20;
                          string emailLike = "%163.com%";
                          string selectConditionSql = "SELECT * FROM Users WHERE Age >= @MinAge AND Email LIKE @EmailLike";
                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              conn.Open();
                              // 多参数:使用匿名对象传递
                              var users = conn.Query<User>(selectConditionSql, new { MinAge = minAge, EmailLike = emailLike }).ToList();
                          }

                          (3)修改(Update)

                          采用Execute方法执行更新SQL,得到受影响的行数:

                          // 准备修改数据
                          int updateId = 1;
                          var updateUser = new User
                          {
                              UserName = "张三_修改",
                              Age = 26,
                              Email = "[email protected]"
                          };
                          string updateSql = @"UPDATE Users
                                              SET UserName = @UserName, Age = @Age, Email = @Email
                                              WHERE Id = @Id";
                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              conn.Open();
                              // 传递多个参数(匿名对象)
                              int affectedRows = conn.Execute(updateSql, new { updateUser.UserName, updateUser.Age, updateUser.Email, Id = updateId });
                              Console.WriteLine($"修改成功,受影响行数:{affectedRows}");
                          }

                          (4)删除(Delete)

                          同样采用Execute方法,得到受影响的行数:

                          int deleteId = 1;
                          string deleteSql = "DELETE FROM Users WHERE Id = @Id";
                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              conn.Open();
                              int affectedRows = conn.Execute(deleteSql, new { Id = deleteId });
                              Console.WriteLine($"删除成功,受影响行数:{affectedRows}");
                          }

                          4. 核心API总结(必记)

                          在这个场景下,Dapper的API极其简洁,核心方法仅10+个,以下是最常用的API,记熟即可覆盖90%的场景:

                          API方法作用得到值
                          Query(sql, param)执行查询SQL,得到多个结果IEnumerable
                          QueryFirstOrDefault(sql, param)执行查询SQL,得到第一个结果,无结果得到nullT
                          QuerySingleOrDefault(sql, param)执行查询SQL,得到单个结果(若多个结果抛出异常)T
                          Execute(sql, param)执行增删改SQL、存储过程int(受影响行数)
                          ExecuteScalar(sql, param)执行SQL,得到单个值(如自增ID、统计结果)T
                          QueryMultiple(sql, param)执行多个SQL语句,得到多个结果集SqlMapper.GridReader

                          三、核心特性:Dapper进阶用法(深度重点)

                          理解这一步时,基础CRUD只能满足轻松场景,实际项目中常遇到联表查询、多结果集、存储过程、事务处理等复杂需求,以下是Dapper最核心的进阶特性,也是体现其灵活性和强大性的关键。

                          1. 参数化查询详解(防SQL注入,必掌握)

                          理解这一步时,SQL注入是Web开发中最常用的安全隐患,Dapper的参数化查询彻底解决了这一问题,兼容3种参数传递方式,适配不同场景:

                          (1)匿名对象参数(最常用)

                          // 适用于参数较少的场景
                          string sql = "SELECT * FROM Users WHERE Age >= @MinAge AND UserName LIKE @UserName";
                          var users = conn.Query<User>(sql, new { MinAge = 20, UserName = "%张%" }).ToList();

                          (2)实体类参数(适用来参数较多的场景)

                          // 实体类的属性名与SQL中的@参数名一致即可
                          var param = new User { Age = 20, UserName = "张三" };
                          string sql = "SELECT * FROM Users WHERE Age = @Age AND UserName = @UserName";
                          var user = conn.QueryFirstOrDefault<User>(sql, param);

                          (3)DynamicParameters参数(适用来动态参数、多参数场景)

                          从实现思路看,参数个数不确定、需动态添加参数时,采用DynamicParameters类:

                          var param = new DynamicParameters();
                          param.Add("@MinAge", 20); // 添加参数
                          param.Add("@MaxAge", 30);
                          param.Add("@UserName", "%张%", DbType.String, ParameterDirection.Input, 50); // 指定参数类型、方向、长度
                          string sql = "SELECT * FROM Users WHERE Age BETWEEN @MinAge AND @MaxAge AND UserName LIKE @UserName";
                          var users = conn.Query<User>(sql, param).ToList();

                          2. 多结果集查询(QueryMultiple)

                          结合项目来看,实际项目中,经常需一次性执行多个SQL语句,拿到多个结果集(如同时查询用户表和订单表),Dapper的QueryMultiple方法可高效实现,避免多次数据库连接:

                          // 一次性执行两个SQL语句,获取两个结果集
                          string sql = @"SELECT * FROM Users WHERE Id = @Id;
                                        SELECT * FROM Orders WHERE UserId = @Id;";
                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              conn.Open();
                              // 执行多结果集查询
                              using (var multi = conn.QueryMultiple(sql, new { Id = 1 }))
                              {
                                  // 读取第一个结果集(用户)
                                  User user = multi.Read<User>().FirstOrDefault();
                                  // 读取第二个结果集(该用户的订单)
                                  List<Order> orders = multi.Read<Order>().ToList();
                                  // 关联数据
                                  if (user != null)
                                  {
                                      user.Orders = orders;
                                  }
                              }
                          }

                          关键说明:Read<T>()方法用来读取当前结果集,每次调用读取下一个结果集,顺序与SQL语句顺序一致。

                          3. 联表查询与实体映射(复杂场景)

                          实际处理时,联表查询是实际项目中最常用的复杂查询场景,Dapper兼容两种映射方式:“手动映射”和“自动映射”,适配不同复杂度的联表需求。

                          (1)轻松联表(自动映射)

                          联表查询的结果字段与实体类属性名一致时,可直接映射:

                          -- 联表查询:用户表 + 角色表
                          SELECT u.Id, u.UserName, u.Age, r.RoleName
                          FROM Users u
                          LEFT JOIN Roles r ON u.RoleId = r.Id
                          WHERE u.Id = @Id

                          // 定义包含联表字段的实体类(可新增DTO)
                          public class UserWithRole
                          {
                              public int Id { get; set; }
                              public string UserName { get; set; }
                              public int Age { get; set; }
                              public string RoleName { get; set; } // 角色表的字段
                          }
                          // 自动映射联表结果
                          string sql = @"SELECT u.Id, u.UserName, u.Age, r.RoleName
                                        FROM Users u
                                        LEFT JOIN Roles r ON u.RoleId = r.Id
                                        WHERE u.Id = @Id";
                          UserWithRole user = conn.QueryFirstOrDefault<UserWithRole>(sql, new { Id = 1 });

                          (2)复杂联表(手动映射)

                          实际处理时,联表结果需映射到嵌套实体(如用户包含角色列表)时,需采用Dapper的Query<T1, T2, T3>方法手动指定映射逻辑:

                          // 实体类(用户包含角色列表)
                          public class User
                          {
                              public int Id { get; set; }
                              public string UserName { get; set; }
                              public List<Role> Roles { get; set; } = new List<Role>();
                          }
                          public class Role
                          {
                              public int Id { get; set; }
                              public string RoleName { get; set; }
                          }
                          // 联表查询SQL
                          string sql = @"SELECT u.Id, u.UserName, r.Id AS RoleId, r.RoleName
                                        FROM Users u
                                        LEFT JOIN UserRoles ur ON u.Id = ur.UserId
                                        LEFT JOIN Roles r ON ur.RoleId = r.Id
                                        WHERE u.Id = @Id";
                          // 手动映射:将查询结果映射到User和Role,关联角色列表
                          var users = conn.Query<User, Role, User>(
                              sql,
                              (user, role) => // 映射委托:第一个参数是主实体,第二个是关联实体
                              {
                                  if (role != null)
                                  {
                                      user.Roles.Add(role);
                                  }
                                  return user;
                              },
                              param: new { Id = 1 },
                              splitOn: "RoleId" // 拆分字段:指定从哪个字段开始拆分第二个实体(Role)
                          ).Distinct().ToList(); // Distinct去重(避免用户被重复返回)

                          关键说明:splitOn参数是手动映射的核心,用来指定“主实体”和“关联实体”的拆分字段,必须是查询结果中存在的字段(如RoleId)。

                          4. 存储过程调用(企业级项目常用)

                          理解这一步时,企业级项目中,大量复杂业务逻辑会封装在存储过程中,Dapper对存储过程的兼容很完善,兼容输入参数、输出参数、得到值。

                          示例:调用存储过程查询用户

                          -- 创建存储过程
                          CREATE PROCEDURE GetUserById
                              @Id INT -- 输入参数
                          AS
                          BEGIN
                              SELECT * FROM Users WHERE Id = @Id
                          END

                          // 调用存储过程(指定commandType为StoredProcedure)
                          int userId = 1;
                          var user = conn.QueryFirstOrDefault<User>(
                              "GetUserById", // 存储过程名称
                              new { Id = userId }, // 输入参数
                              commandType: CommandType.StoredProcedure // 关键:指定为存储过程
                          );

                          示例:调用带输出参数的存储过程

                          -- 创建带输出参数的存储过程
                          CREATE PROCEDURE GetUserCountByAge
                              @MinAge INT, -- 输入参数
                              @Count INT OUTPUT -- 输出参数
                          AS
                          BEGIN
                              SELECT @Count = COUNT(*) FROM Users WHERE Age >= @MinAge
                          END

                          // 使用DynamicParameters传递输入/输出参数
                          var param = new DynamicParameters();
                          param.Add("@MinAge", 20); // 输入参数
                          param.Add("@Count", 0, DbType.Int32, ParameterDirection.Output); // 输出参数(初始值0)
                          // 调用存储过程
                          conn.Execute(
                              "GetUserCountByAge",
                              param,
                              commandType: CommandType.StoredProcedure
                          );
                          // 获取输出参数的值
                          int count = param.Get<int>("@Count");
                          Console.WriteLine($"年龄>=20的用户数量:{count}");

                          5. 事务处理(保证数据一致性)

                          落到代码里,需执行多个SQL操作(如新增用户同时新增角色关联)时,需采用事务保证数据一致性,Dapper兼容两种事务方式:IDbTransaction(原生事务)和TransactionScope(分布式事务)。

                          (1)原生事务(IDbTransaction)

                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              conn.Open();
                              // 开启事务
                              using (IDbTransaction tran = conn.BeginTransaction())
                              {
                                  try
                                  {
                                      // 执行第一个SQL(新增用户)
                                      string insertUserSql = "INSERT INTO Users (UserName, Age) VALUES (@UserName, @Age); SELECT SCOPE_IDENTITY()";
                                      int userId = conn.ExecuteScalar<int>(insertUserSql, new { UserName = "李四", Age = 28 }, transaction: tran);
                                      // 执行第二个SQL(新增用户角色关联)
                                      string insertUserRoleSql = "INSERT INTO UserRoles (UserId, RoleId) VALUES (@UserId, @RoleId)";
                                      conn.Execute(insertUserRoleSql, new { UserId = userId, RoleId = 1 }, transaction: tran);
                                      // 提交事务
                                      tran.Commit();
                                      Console.WriteLine("事务执行成功");
                                  }
                                  catch (Exception ex)
                                  {
                                      // 回滚事务(出现异常时)
                                      tran.Rollback();
                                      Console.WriteLine($"事务执行失败:{ex.Message}");
                                  }
                              }
                          }

                          (2)分布式事务(TransactionScope)

                          落到代码里,需跨多个数据库、多个连接执行事务时,采用TransactionScope(需引用System.Transactions包):

                          // 需安装NuGet包:System.Transactions
                          using (var scope = new TransactionScope())
                          {
                              // 第一个数据库连接
                              using (IDbConnection conn1 = new SqlConnection(connectionString1))
                              {
                                  conn1.Open();
                                  conn1.Execute("INSERT INTO Users (UserName) VALUES (@UserName)", new { UserName = "王五" });
                              }
                              // 第二个数据库连接(跨库事务)
                              using (IDbConnection conn2 = new SqlConnection(connectionString2))
                              {
                                  conn2.Open();
                                  conn2.Execute("INSERT INTO Orders (OrderNo) VALUES (@OrderNo)", new { OrderNo = "20240520001" });
                              }
                              // 提交事务(所有操作成功才提交)
                              scope.Complete();
                          }

                          四、高级技巧:Dapper扩展与最佳实践

                          落到代码里,Dapper本身轻量,但借助扩展包和最佳实践,可进一步提升开发效率,适配更复杂的项目场景。

                          1. Dapper扩展包建议(必装)

                          Dapper官方提供了多个扩展包,简化重复操作,最常用的有两个:

                          (1)Dapper.Contrib(简化CRUD,无需写SQL)

                          在这个场景下,Dapper.Contrib提供了InsertUpdateDeleteGet等方法,无需手动编写SQL,自动生成基础CRUD语句,适合轻松CRUD场景:

                          // 安装扩展包:Install-Package Dapper.Contrib
                          using Dapper.Contrib.Extensions;
                          // 实体类需标注[Table]特性(指定数据库表名,可选)
                          [Table("Users")]
                          public class User
                          {
                              [Key] // 标注主键(自增主键需标注)
                              public int Id { get; set; }
                              public string UserName { get; set; }
                              public int Age { get; set; }
                          }
                          // 简化CRUD操作(无需写SQL)
                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              conn.Open();
                              // 新增
                              int newId = conn.Insert<int>(new User { UserName = "赵六", Age = 30 });
                              // 根据ID查询
                              User user = conn.Get<User>(newId);
                              // 修改
                              user.UserName = "赵六_修改";
                              conn.Update(user);
                              // 删除
                              conn.Delete(user);
                              // 查询所有
                              List<User> users = conn.GetAll<User>().ToList();
                          }

                          (2)Dapper.SqlBuilder(动态构建SQL)

                          实际处理时,SQL语句需根据条件动态拼接(如多条件筛选、动态排序)时,采用Dapper.SqlBuilder可避免手动拼接SQL的繁琐和风险:

                          // 安装扩展包:Install-Package Dapper.SqlBuilder
                          using Dapper.SqlBuilder;
                          // 构建动态SQL
                          var builder = new SqlBuilder();
                          // 基础SQL
                          var template = builder.AddTemplate(@"SELECT * FROM Users /**where** /**orderby**");
                          // 动态添加WHERE条件
                          int? minAge = 20;
                          string userName = "张";
                          if (minAge.HasValue)
                          {
                              builder.Where("Age >= @MinAge", new { MinAge = minAge });
                          }
                          if (!string.IsNullOrEmpty(userName))
                          {
                              builder.Where("UserName LIKE @UserName", new { UserName = $"%{userName}%" });
                          }
                          // 动态添加排序
                          builder.OrderBy("CreateTime DESC");
                          // 执行查询
                          using (IDbConnection conn = new SqlConnection(connectionString))
                          {
                              conn.Open();
                              var users = conn.Query<User>(template.RawSql, template.Parameters).ToList();
                          }

                          2. 项目中Dapper的最佳实践

                          落到代码里,在真实项目中,合理采用Dapper可提升代码可维护性和性能,以下是企业级项目中的最佳实践:

                          (1)封装Dapper工具类(全局单例)

                          从实现思路看,将数据库连接、基础CRUD操作封装为工具类,避免重复代码,统一管理连接字符串和异常处理:

                          public static class DapperHelper
                          {
                              // 连接字符串(从配置文件读取,推荐使用IConfiguration)
                              private static readonly string _connectionString = "Data Source=.;Initial Catalog=DapperDemo;User ID=sa;Password=123456;";
                              // 执行增删改操作
                              public static int Execute(string sql, object param = null, IDbTransaction transaction = null)
                              {
                                  using (IDbConnection conn = new SqlConnection(_connectionString))
                                  {
                                      conn.Open();
                                      return conn.Execute(sql, param, transaction);
                                  }
                              }
                              // 执行查询操作(返回多个结果)
                              public static List<T> Query<T>(string sql, object param = null, IDbTransaction transaction = null)
                              {
                                  using (IDbConnection conn = new SqlConnection(_connectionString))
                                  {
                                      conn.Open();
                                      return conn.Query<T>(sql, param, transaction).ToList();
                                  }
                              }
                              // 执行查询操作(返回单个结果)
                              public static T QueryFirstOrDefault<T>(string sql, object param = null, IDbTransaction transaction = null)
                              {
                                  using (IDbConnection conn = new SqlConnection(_connectionString))
                                  {
                                      conn.Open();
                                      return conn.QueryFirstOrDefault<T>(sql, param, transaction);
                                  }
                              }
                              // 其他方法...
                          }

                          (2)分层架构中采用Dapper(DAL层封装)

                          实际处理时,在分层架构(UI→BLL→DAL)中,将Dapper操作封装在DAL层,BLL层调用DAL层方法,解耦业务逻辑与数据访问:

                          // DAL层(数据访问层)
                          public class UserDal
                          {
                              public User GetById(int id)
                              {
                                  string sql = "SELECT * FROM Users WHERE Id = @Id";
                                  return DapperHelper.QueryFirstOrDefault<User>(sql, new { Id = id });
                              }
                              public int Add(User user)
                              {
                                  string sql = "INSERT INTO Users (UserName, Age, Email) VALUES (@UserName, @Age, @Email); SELECT SCOPE_IDENTITY()";
                                  return DapperHelper.ExecuteScalar<int>(sql, user);
                              }
                              // 其他方法...
                          }
                          // BLL层(业务逻辑层)
                          public class UserBll
                          {
                              private readonly UserDal _userDal = new UserDal();
                              public User GetUserById(int id)
                              {
                                  // 业务逻辑处理
                                  if (id <= 0)
                                  {
                                      throw new ArgumentException("用户ID无效");
                                  }
                                  return _userDal.GetById(id);
                              }
                              // 其他方法...
                          }

                          (3)SQL语句优化(提升性能)

                          • 避免SELECT *:只查询需的字段,减少数据传输量和内存占用。
                          • 采用索引:查询条件(WHERE、JOIN)的字段需建立索引,提升查询速度。
                          • 批量操作:批量新增/修改时,采用Execute方法一次性执行,避免循环调用(如批量新增1000条数据,拼接SQL一次性执行)。
                          • 分页查询:采用分页SQL(如SQL Server的OFFSET/FETCH),避免查询全部数据再分页。

                          五、性能优化:让Dapper跑得更快(核心重点)

                          从实现思路看,Dapper本身性能接近原生ADO.NET,但如果采用不当,仍会出现性能瓶颈,以下是最实用的性能优化技巧,覆盖“连接管理、SQL优化、映射优化”三个核心维度。

                          1. 连接管理优化(最关键)

                          • 采用连接池:.NET默认启用数据库连接池(连接字符串中Pooling=true,默认开启),无需手动管理连接池,避免频繁新建/关闭连接。
                          • 手动打开连接:Dapper的Query、Execute方法会自动打开连接,但建议手动调用conn.Open(),尤其是批量操作,避免多次打开/关闭连接的开销。
                          • 避免长连接:不要长时间持有数据库连接(如全局连接),采用using语句自动释放,避免连接泄漏。

                          2. 映射优化(减少反射开销)

                          理解这一步时,Dapper的映射依赖反射,虽然反射开销极低,但高频调用场景下仍需优化:

                          • 采用TypeHandler自定义类型映射:对于自定义类型(如枚举、DateTimeOffset),自定义TypeHandler,避免Dapper自动反射解析。
                          • 避免匿名对象映射:高频查询场景下,采用实体类映射,比匿名对象映射更快(匿名对象需动态新建类型)。
                          • 启用Dapper缓存:Dapper会自动缓存SQL语句的解析结果,无需手动设置,确保SQL语句格式一致(避免重复解析)。

                          3. SQL优化(核心中的核心)

                          • 参数化查询:不仅防SQL注入,还能让数据库缓存查询计划,提升重复查询的性能。
                          • 避免复杂子查询:复杂子查询会增加数据库计算开销,尽量采用JOIN替代子查询。
                          • 批量操作优化:批量新增/修改时,采用Execute方法一次性执行多条SQL(用分号分隔),或采用表值参数(SQL Server),避免循环调用。
                          • 分页优化:采用数据库原生分页(如SQL Server的OFFSET/FETCH、MySQL的LIMIT),避免查询全部数据再分页。

                          4. 高频场景优化示例

                          高频查询场景(如首页列表查询),优化方案:

                          // 优化前:查询全部字段,无分页
                          string sql = "SELECT * FROM Users WHERE Age >= 20";
                          var users = DapperHelper.Query<User>(sql);
                          // 优化后:只查需要的字段,分页查询,使用索引
                          string sql = "SELECT Id, UserName, Age FROM Users WHERE Age >= @MinAge ORDER BY CreateTime DESC OFFSET @PageSize * (@PageIndex - 1) ROWS FETCH NEXT @PageSize ROWS ONLY";
                          var users = DapperHelper.Query<User>(sql, new { MinAge = 20, PageIndex = 1, PageSize = 10 });

                          六、避坑指南:常用问题与解决方案

                          结合项目来看,Dapper用法轻松,但实际项目中仍会遇到一些坑,以下是最常用的问题及解决方案,帮你避免踩坑。

                          1. 问题1:参数名与实体类属性名不一致,映射失败

                          【原因】:Dapper默认按“属性名=参数名”映射,若SQL中的参数名与实体类属性名不一致,会导致映射失败(得到null或默认值)。

                          【解决方案】:

                          // 方案1:修改SQL参数名,与实体类属性名一致
                          string sql = "SELECT * FROM Users WHERE UserName = @UserName"; // 与实体类的UserName一致
                          // 方案2:使用别名,让查询结果字段与实体类属性名一致
                          string sql = "SELECT UserName AS Name FROM Users"; // 实体类属性名为Name
                          // 方案3:手动映射(复杂场景)
                          var user = conn.QueryFirstOrDefault<User>(
                              "SELECT UserName AS Name FROM Users WHERE Id = @Id",
                              new { Id = 1 }
                          );

                          2. 问题2:自增主键得到失败(SQL Server)

                          【原因】:插入SQL未添加SELECT SCOPE_IDENTITY(),或采用Execute方法而非ExecuteScalar

                          【解决方案】:

                          // 正确写法:插入后返回自增ID,使用ExecuteScalar
                          string sql = "INSERT INTO Users (UserName) VALUES (@UserName); SELECT SCOPE_IDENTITY()";
                          int newId = conn.ExecuteScalar<int>(sql, new { UserName = "张三" });

                          3. 问题3:多结果集读取顺序错误,导致映射失败

                          【原因】:Read<T>方法的调用顺序与SQL语句中结果集的顺序不一致。

                          【解决方案】:确保Read<T>的顺序与SQL语句中多个SQL的顺序一致:

                          // SQL顺序:先查Users,再查Orders
                          string sql = "SELECT * FROM Users; SELECT * FROM Orders;";
                          using (var multi = conn.QueryMultiple(sql))
                          {
                              var users = multi.Read<User>().ToList(); // 先读Users(与SQL顺序一致)
                              var orders = multi.Read<Order>().ToList(); // 再读Orders
                          }

                          4. 问题4:事务提交后,数据未生效

                          【原因】:执行SQL时未指定事务对象,导致SQL执行在事务之外。

                          【解决方案】:执行SQL时,将transaction参数指定为开启的事务对象:

                          using (var tran = conn.BeginTransaction())
                          {
                              // 关键:指定transaction参数
                              conn.Execute(insertSql, param, transaction: tran);
                              tran.Commit();
                          }

                          5. 问题5:高频调用时性能下降

                          【原因】:频繁新建连接、SQL语句未缓存、未采用索引、查询字段过多。

                          【解决方案】:

                          • 采用连接池,手动打开连接,避免频繁新建/关闭。
                          • 确保SQL语句格式一致,让Dapper缓存解析结果。
                          • 查询条件字段建立索引,只查询需的字段。

                          七、总结

                          Dapper的核心价值的是“轻量、高效、灵活理解这一步时,”,它不追求“大而全”,而是专注于解决数据映射的核心痛点,在保持原生SQL控制力的同时,简化开发流程。掌握以下核心要点,即可在项目里高效采用Dapper:

                          • 基础核心:借助IDbConnection的扩展方法(Query、Execute)执行SQL,参数化查询防SQL注入。
                          • 进阶特性:多结果集(QueryMultiple)、联表映射、存储过程调用、事务处理,适配复杂场景。
                          • 扩展工具:Dapper.Contrib简化CRUD,Dapper.SqlBuilder动态构建SQL,提升开发效率。
                          • 性能优化:合理管理连接、优化SQL语句、减少反射开销,发挥Dapper的性能优势。
                          • 避坑关键:注意参数名与属性名一致、自增主键得到方式、事务参数指定、多结果集读取顺序。

                          在这个场景下,Dapper不是EF Core的替代品,而是互补品——在高性能、复杂SQL、微服务等场景中,Dapper是最优选择;在更快开发、轻松CRUD场景中,EF Core更便捷。合理选择工具,才能让开发效率与系统性能达到平衡。

                          到此这篇关于C#常用类库Dapper详解的文章就介绍到这了,更多相关C#常用类库Dapper内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多兼容脚本之家!

                          您可能感兴趣的文章:

                          • C# 采用Dapper与金仓数据库交互的实现步骤
                          • C#采用Dapper存取数据库详解
                          • C#中采用Dapper进行数据库访问的流程步骤
                          • C#中Dapper的采用教程
                          • 如何在C#中采用Dapper ORM
                          • 在.NetCore(C#)中采用ODP.NET Core+Dapper操作Oracle数据库
                          • 在C#中如何采用Dapper详解(译)

                          相关资讯
                          点击查看更多
                          游戏推荐
                          推荐专题
                          热门阅读
                          推荐下载