在C#应用中操作数据库时,删除数据是最常见的写操作之一。不同于查询,删除具备破坏性与不可逆特征,一旦条件写错就可能清掉整张表。因此理解C#执行SQL删除的各类方式、参数化处理与事务控制,是后端开发的基本功。下面从最基础的ADO.NET用法讲起,逐步覆盖批量删除、异步执行以及第三方库方案。

一、使用ADO.NET执行基础删除
ADO.NET是.NET原生的数据访问组件,通过SqlConnection和SqlCommand即可完成删除。核心思路是先建立连接,再创建命令对象,把DELETE语句与参数传入,最后调用ExecuteNonQuery获取受影响行数。
下面示例演示删除指定ID的用户记录。注意这里使用@Id参数,而不是把值拼进SQL字符串,这能彻底阻隔SQL注入。即使传入的字符串包含恶意片段,也只会被当作参数值处理。
using System;
using System.Data.SqlClient;
class Demo
{
static void DeleteUser(int id)
{
string connStr = "Server=127.0.0.1;Database=TestDb;User Id=sa;Password=123;";
using (var conn = new SqlConnection(connStr))
{
conn.Open();
string sql = "DELETE FROM Users WHERE Id = @Id";
using (var cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@Id", id);
int rows = cmd.ExecuteNonQuery();
Console.WriteLine("删除了" + rows + "行");
}
}
}
}
上述代码用using确保连接与命令对象被释放,避免连接泄漏。ExecuteNonQuery返回的是被删除的行数,若返回0说明没有匹配记录,业务层可据此提示用户数据不存在。
若不使用参数,而写成string sql = "DELETE FROM Users WHERE Id=" + id,当id来自前端输入且被篡改为"1; DROP TABLE Users"时,后果不堪设想。所以参数化不是可选项,而是强制规范。
二、带条件的批量删除与事务
实际业务中常需按状态批量清理数据,例如删除三十天前且已失效的订单。此时仍然使用参数化,只是WHERE条件更复杂。另外,若一次请求要删多张关联表,就必须用事务包裹,防止只删了主表而明细表残留。
事务通过SqlTransaction实现,在conn.BeginTransaction后,把事务对象赋给命令的Transaction属性。只有调用Commit才会真正生效,任何异常时执行Rollback即可回滚。
using System;
using System.Data.SqlClient;
class BatchDemo
{
static void DeleteOldOrders()
{
string connStr = "Server=127.0.0.1;Database=TestDb;User Id=sa;Password=123;";
using (var conn = new SqlConnection(connStr))
{
conn.Open();
using (var tx = conn.BeginTransaction())
{
try
{
string sql = "DELETE FROM OrderDetail WHERE OrderId IN (SELECT Id FROM Orders WHERE Status=@St AND CreateTime<@Limit)";
using (var cmd = new SqlCommand(sql, conn, tx))
{
cmd.Parameters.AddWithValue("@St", 0);
cmd.Parameters.AddWithValue("@Limit", DateTime.Now.AddDays(-30));
cmd.ExecuteNonQuery();
}
string sql2 = "DELETE FROM Orders WHERE Status=@St AND CreateTime<@Limit";
using (var cmd2 = new SqlCommand(sql2, conn, tx))
{
cmd2.Parameters.AddWithValue("@St", 0);
cmd2.Parameters.AddWithValue("@Limit", DateTime.Now.AddDays(-30));
cmd2.ExecuteNonQuery();
}
tx.Commit();
}
catch (Exception ex)
{
tx.Rollback();
Console.WriteLine("删除失败:" + ex.Message);
}
}
}
}
}
上面的代码先删明细再删主表,事务保证两步同生共死。若明细删除成功但主表因外键冲突报错,Rollback会让明细也还原,数据库状态保持一致。
需要提醒的是,大批量删除会锁表并写满日志。生产环境建议分批次删除,比如每次删一千行并短暂休眠,降低对线上读写的影响。
三、异步删除与Dapper简化
在Web接口中,同步删除会占用线程池线程。C#提供了ExecuteNonQueryAsync等异步方法,配合async/await可提升吞吐。另外,Dapper这类轻量ORM能把参数映射和对象转换简化成几行代码。
下面先用原生异步展示,再用Dapper对比。异步版本把Open和Execute都换成Async后缀,并在调用处await,避免阻塞请求线程。
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
class AsyncDemo
{
static async Task DeleteUserAsync(int id)
{
string connStr = "Server=127.0.0.1;Database=TestDb;User Id=sa;Password=123;";
using (var conn = new SqlConnection(connStr))
{
await conn.OpenAsync();
string sql = "DELETE FROM Users WHERE Id = @Id";
using (var cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@Id", id);
int rows = await cmd.ExecuteNonQueryAsync();
Console.WriteLine("异步删除行数:" + rows);
}
}
}
}
Dapper只需一个Execute调用,它内部仍走参数化,不会引入注入风险。对于习惯写POCO对象的团队,可以少写大量样板代码。
using System;
using System.Data.SqlClient;
using Dapper;
class DapperDemo
{
static void DeleteWithDapper(int id)
{
string connStr = "Server=127.0.0.1;Database=TestDb;User Id=sa;Password=123;";
using (var conn = new SqlConnection(connStr))
{
int rows = conn.Execute("DELETE FROM Users WHERE Id = @Id", new { Id = id });
Console.WriteLine("Dapper删除行数:" + rows);
}
}
}
从对比可见,Dapper把连接、命令、参数添加压缩到一行,同时保留参数化安全特性。但在极复杂动态条件删除时,仍建议手写SQL片段拼接参数,或借助Dapper的DynamicParameters灵活传参。
无论用哪种方式,上线前都应在测试库用SELECT验证WHERE命中范围,确认不会误伤数据。必要时先备份再做删除,尤其是没有软删除设计的老系统。
四、常见误区与避坑清单
不少初学者认为DELETE比DROP安全,便在管理后台暴露无条件的删除入口,结果运维点错清空全表。其实DELETE不带WHERE就是全表删除,且会逐行记日志,比TRUNCATE更慢更危险。
另一个误区是滥用AddWithValue导致类型推断偏差,例如传入长整型却被推断为int,使参数匹配失败。对关键字段建议显式指定SqlDbType与尺寸,如cmd.Parameters.Add("@Id", SqlDbType.Int).Value = id。
| 做法 | 风险 | 建议 |
|---|---|---|
| 字符串拼接SQL | SQL注入、删库 | 始终参数化 |
| 无WHERE删除 | 全表清空 | 加保护确认或软删除 |
| 大事务长删除 | 锁表阻塞 | 分批或低峰执行 |
最后,对于核心业务数据,推荐引入软删除字段如IsDeleted,用UPDATE替代DELETE,既满足合规审计,也降低误操作成本。真正物理删除只留给日志类或临时表。
掌握上述C#执行SQL删除的多种写法与防护手段后,你就能在控制台、Web API或后台任务中稳妥地清理数据,既高效又不怕踩坑。