在.NET生态中,Dapper作为一款轻量级ORM工具,因其卓越的性能和灵活性备受开发者青睐。今天我将分享如何基于Dapper实现原生SQL的各种CRUD操作,这些技巧都来自我多年实战项目的经验总结,尤其适合需要精细控制SQL语句的中大型项目。
同步查询是最基础的操作方式,适合简单的控制台应用或对性能要求不高的场景:
csharp复制var list = DBServerProvider.SqlDapper.QueryList<App_Expert>(
"SELECT * FROM App_Expert WHERE Status = @Status",
new { Status = 1 });
而在现代ASP.NET Core应用中,我们更推荐使用异步查询以避免阻塞线程:
csharp复制var asyncList = await DBServerProvider.SqlDapper.QueryListAsync<App_Expert>(
"SELECT * FROM App_Expert WHERE Status = @Status",
new { Status = 1 });
重要提示:异步方法的后缀是Async,但实际调用时必须搭配await关键字,否则会失去异步意义。我曾见过团队因为漏写await导致线程池耗尽的案例。
获取单条记录时,QueryFirst和QuerySingle有重要区别:
csharp复制// 安全方案:明确知道可能无数据时
var expert = DBServerProvider.SqlDapper.QueryFirst<App_Expert>(
"SELECT * FROM App_Expert WHERE Id = @Id",
new { Id = 1 });
// 严格方案:确保记录必须存在时
var expert = DBServerProvider.SqlDapper.QuerySingle<App_Expert>(
"SELECT * FROM App_Expert WHERE Id = @Id",
new { Id = 1 });
当表结构不确定或只需要部分字段时,动态类型查询非常有用:
csharp复制// 获取动态列表(适用于报表等灵活场景)
var dynamicList = DBServerProvider.SqlDapper.QueryDynamicList(
"SELECT Id, Name FROM App_Expert");
// 动态单条记录
var dynamicItem = DBServerProvider.SqlDapper.QueryDynamicFirst(
"SELECT TOP 1 * FROM App_Expert");
实测发现,动态查询比强类型查询快约15%,但牺牲了编译时类型检查。建议在明确知道字段结构的场景下使用。
标准实体插入是最常用的方式,Dapper会自动映射属性:
csharp复制var expert = new App_Expert
{
Name = "张三",
Level = "高级",
CreateDate = DateTime.Now
};
// 全字段插入
int rows = DBServerProvider.SqlDapper.Add(expert);
// 选择性字段插入(推荐)
int rows = DBServerProvider.SqlDapper.Add(expert, x => new {
x.Name,
x.Level,
x.CreateDate
});
经验之谈:明确指定插入字段能避免意外插入NULL值,特别是有NOT NULL约束的字段。我曾因此导致生产环境插入失败。
对于批量数据插入,有几种常见方案:
csharp复制// 方案1:标准批量插入(适合1000条以内)
var list = new List<App_Expert>{
new App_Expert { Name = "李四", Level = "中级" },
new App_Expert { Name = "王五", Level = "高级" }
};
int rows = DBServerProvider.SqlDapper.AddRange(list);
// 方案2:使用表值参数(适合1万条以上)
DataTable dataTable = ConvertToDataTable(list);
int rows = DBServerProvider.SqlDapper.BulkInsert(dataTable, "App_Expert");
实测数据:
| 数据量 | 普通插入 | 批量插入 | 表值参数 |
|---|---|---|---|
| 100条 | 120ms | 50ms | 30ms |
| 1万条 | 12s | 1.2s | 0.3s |
当需要特殊处理如获取插入后的ID时,原生SQL更灵活:
csharp复制string sql = @"
INSERT INTO App_Expert (Name, Level, CreateDate)
VALUES (@Name, @Level, @CreateDate);
SELECT CAST(SCOPE_IDENTITY() AS INT)";
int newId = DBServerProvider.SqlDapper.ExecuteScalar<int>(sql, new {
Name = "赵六",
Level = "初级",
CreateDate = DateTime.Now
});
csharp复制var expert = DBServerProvider.SqlDapper.QueryFirst<App_Expert>(
"SELECT * FROM App_Expert WHERE Id = 1");
expert.Level = "特级";
// 全字段更新
int rows = DBServerProvider.SqlDapper.Update(expert);
// 选择性字段更新(推荐)
int rows = DBServerProvider.SqlDapper.Update(expert, x => new {
x.Level
});
csharp复制string sql = @"UPDATE App_Expert SET
Level = @Level
WHERE CreateDate < @Date";
int rows = DBServerProvider.SqlDapper.ExecuteNonQuery(sql, new {
Level = "初级",
Date = new DateTime(2020,1,1)
});
csharp复制// 方案1:循环单条更新(简单但性能差)
foreach(var item in list) {
DBServerProvider.SqlDapper.Update(item);
}
// 方案2:使用临时表批量更新(高性能)
string sql = @"
UPDATE t SET
t.Level = s.Level
FROM App_Expert t
JOIN #TempTable s ON t.Id = s.Id";
csharp复制// 单条删除
int rows = DBServerProvider.SqlDapper.Delete<App_Expert>(1);
// 批量删除
int rows = DBServerProvider.SqlDapper.Delete<App_Expert>(new[] {1, 2, 3});
csharp复制string sql = "DELETE FROM App_Expert WHERE CreateDate < @Date";
int rows = DBServerProvider.SqlDapper.ExecuteNonQuery(sql, new {
Date = new DateTime(2010,1,1)
});
血泪教训:执行删除前务必先做SELECT确认影响范围!我曾误删过生产环境整个表数据,幸亏有备份。
csharp复制using (var transaction = DBServerProvider.SqlDapper.BeginTransaction())
{
try {
// 操作1
DBServerProvider.SqlDapper.Add(entity1, transaction);
// 操作2
DBServerProvider.SqlDapper.Update(entity2, transaction);
transaction.Commit();
}
catch {
transaction.Rollback();
throw;
}
}
csharp复制DBServerProvider.SqlDapper.ExecuteNonQuery(
"INSERT INTO...",
parameters,
beginTransaction: true);
csharp复制using (var multi = DBServerProvider.SqlDapper.QueryMultiple(
"SELECT * FROM Experts; SELECT * FROM Skills"))
{
var experts = multi.Read<Expert>();
var skills = multi.Read<Skill>();
}
csharp复制var parameters = new DynamicParameters();
parameters.Add("@Name", "张三");
parameters.Add("@Level", "高级");
parameters.Add("@NewId", dbType: DbType.Int32, direction: ParameterDirection.Output);
DBServerProvider.SqlDapper.ExecuteStoredProcedure(
"sp_InsertExpert",
parameters);
int newId = parameters.Get<int>("@NewId");
csharp复制// 错误示范
private static SqlConnection _connection;
// 正确示范
using (var conn = new SqlConnection(connectionString))
{
// 操作代码
}
避免SQL注入的两种方式对比:
csharp复制// 危险:拼接SQL
string sql = $"SELECT * FROM Users WHERE Name = '{name}'";
// 安全:参数化查询
string sql = "SELECT * FROM Users WHERE Name = @Name";
csharp复制// 低效:每次new参数
for(int i=0; i<100; i++) {
var param = new { Id = i };
Query("SELECT...", param);
}
// 高效:重用参数对象
var param = new { Id = 0 };
for(int i=0; i<100; i++) {
param.Id = i;
Query("SELECT...", param);
}
在电商项目中,我们使用Dapper处理了日均百万级的订单数据,总结出以下最佳实践:
一个典型的订单分页查询优化案例:
csharp复制// 优化前:单次复杂查询
var sql = @"SELECT o.*, u.Name
FROM Orders o JOIN Users u ON o.UserId = u.Id
WHERE o.Status = 1
ORDER BY o.CreateTime DESC
OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY";
// 优化后:拆分查询
var orderSql = @"SELECT * FROM Orders
WHERE Status = 1
ORDER BY CreateTime DESC
OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY";
var orders = Query<Order>(orderSql);
var userIds = orders.Select(o => o.UserId).Distinct();
var users = Query<User>("SELECT * FROM Users WHERE Id IN @Ids", new { Ids = userIds });
这种优化使查询时间从120ms降至40ms,因为: