C#执行SQL插入必须用SqlCommand配合参数化查询,禁用字符串拼接;应选ExecuteNonQuery()获取影响行数;参数需正确绑定,NULL传DBNull.Value,日期用DateTime类型;事务需显式管理,批量插入宜用SqlBulkCopy;异步操作须全程await且连接也需OpenAsync()。

直接说结论:C# 执行 SQL 插入,必须用 SqlCommand 配合参数化查询,绝不能拼接字符串;否则必然面临 SQL 注入风险,且在含单引号、NULL、日期等值时会报错或写入异常。
为什么 ExecuteNonQuery() 是插入操作的唯一合理选择
执行 INSERT 语句不返回结果集,只关心“影响了几行”,ExecuteNonQuery() 正是为此设计。用 ExecuteReader() 会抛出 InvalidOperationException: “该命令的执行结果不是结果集”;用 ExecuteScalar() 则返回 null 或意外值,毫无意义。
-
ExecuteNonQuery()返回int—— 实际插入的行数,可用于判断是否成功(如返回 0 表示 WHERE 不匹配或触发器拦截) - 它自动处理语句类型识别,无需手动设
CommandType(默认就是Text) - 若需获取自增 ID,必须搭配
SELECT SCOPE_IDENTITY()或OUTPUT INSERTED.Id,不能依赖ExecuteNonQuery()返回值
参数化插入怎么写才不出错
常见错误是把参数名写成 @name 却传入 "@name" 字符串,或漏掉 Parameters.Add() 步骤。正确写法是:参数名在 SQL 中用 @param,添加时用 new SqlParameter("@param", value) 或更安全的 AddWithValue()(但注意隐式类型推断陷阱)。
- 字符串值必须用
SqlParameter包装,不可写成"INSERT INTO t VALUES ('" + name + "')"—— 单引号、反斜杠、SQL 关键字都会崩 - NULL 值要传
DBNull.Value,不能传null(C# null)或字符串"NULL" - 日期字段用
DateTime类型传入,别转成字符串格式(如"2024-01-01"),否则可能因区域设置解析失败 - 避免
AddWithValue()用于数值类型:传int.MaxValue可能被推为int,但数据库列是bigint就会溢出 —— 显式指定SqlDbType.Int64更稳
事务和连接生命周期怎么管
没显式事务时,每条 INSERT 是独立事务;但多条插入需原子性时,必须用 SqlTransaction,且 SqlCommand 必须绑定同一 SqlConnection 和 SqlTransaction 实例,否则报 InvalidOperationException: “该连接不与任何事务相关联”。
-
SqlConnection必须Open()后才能创建事务;Close()或Dispose()会自动回滚未提交事务 - 用
using确保连接和命令及时释放,但别在using块内提前Close()—— 会打断事务 - 批量插入百条以上时,别用循环加单条
ExecuteNonQuery(),改用SqlBulkCopy或表值参数(TVP),否则性能差一个数量级
异步插入为什么不能只改 ExecuteNonQueryAsync()
很多人以为把 ExecuteNonQuery() 换成 ExecuteNonQueryAsync() 就算“支持异步”了,但忘了 SqlConnection 本身也得用 OpenAsync(),且整个调用链(包括 await)必须在 async 方法中,否则会同步阻塞线程池。
- 同步方法混用 async/await 会导致死锁(尤其 ASP.NET Framework 旧项目),务必检查调用栈是否全异步
-
await cmd.ExecuteNonQueryAsync()返回Task<int></int>,不是int—— 忘加await会编译通过但逻辑错乱 - EF Core 的
SaveChangesAsync()底层也是封装了这些,但手写 ADO.NET 时每个环节都得自己对齐异步契约
最常被跳过的细节是:参数顺序无关,但参数名必须和 SQL 中完全一致(大小写敏感取决于数据库排序规则);还有人把 SqlCommand 复用时不清空 Parameters 集合,导致第二次执行带着上次的参数报错。


















