最直接方式是用 SqlConnection 建立连接、SqlCommand 执行查询;需正确配置连接字符串、使用 using 管理资源、参数化查询、SqlDataReader 调用 Read() 后读取,并启用日志调试,UI 中必须用 async/await 异步调用。

用 SqlConnection 和 SqlCommand 执行 SQL 查询最直接
VS 里 C# 查数据库,绕不开 ADO.NET 的基础组合:先建 SqlConnection 连上服务器,再用 SqlCommand 发查询。这不是“推荐方案”,而是底层事实——所有高级封装(比如 EF Core)最终都走这层。
常见错误是连接字符串写错或没开 SQL Server 的 TCP/IP 协议,导致抛出 SqlException: A network-related or instance-specific error...。建议在 VS 的“SQL Server 对象资源管理器”里先连通目标实例,右键“属性”复制连接字符串,再粘贴到代码里。
实操建议:
- 连接字符串必须包含
Server=、Database=、Integrated Security=true(Windows 认证)或User ID=/Password=(SQL 认证) - 务必用
using包裹SqlConnection和SqlCommand,否则连接不释放会快速耗尽池 - 查询语句别拼接字符串,一律用参数化:
cmd.CommandText = "SELECT * FROM Users WHERE Age > @minAge"; cmd.Parameters.AddWithValue("@minAge", 18);
SqlDataReader 读取结果时别忘了 Read() 调用
很多人写完 cmd.ExecuteReader() 就直接访问 reader["Name"],结果报 InvalidOperationException: Invalid attempt to read when no data is present——因为 SqlDataReader 初始化后指针在第一行之前,必须先调用 Read() 移动到首行。
典型场景是只查单条记录(如登录验证),容易误以为 ExecuteReader() 返回的就是数据。其实它返回的是一个可遍历的游标对象。
实操建议:
- 查单行单值优先用
ExecuteScalar(),比如SELECT COUNT(*) FROM Orders - 查多行且需逐字段处理,用
while (reader.Read()) { string name = reader.GetString("Name"); } - 读取前检查列是否存在:
if (reader.GetOrdinal("Email") >= 0) { ... },避免字段名变更导致运行时报错
VS 里调试时看不清 SQL 执行过程?启用 SqlClient 日志
VS 默认不显示 ADO.NET 底层日志,遇到超时或空结果很难判断是 SQL 写错了、参数没传对,还是网络抖动。.NET 5+ 可通过配置开启 Microsoft.Data.SqlClient 的诊断日志。
性能影响很小,但能直接看到实际执行的 SQL、参数值、耗时和连接状态,比打断点看变量高效得多。
实操建议:
- 在
appsettings.json加:"Logging": { "LogLevel": { "Default": "Information", "Microsoft.Data.SqlClient": "Debug" } } - 或者代码中临时启用:
SqlClientEventSource.LogToConsole = true;(需引用Microsoft.Data.SqlClient5.1+) - 注意日志里参数值可能被脱敏(如显示为
@id = [123]),这是默认行为,不影响排查逻辑
别在 UI 线程里同步查数据库,async/await 不是可选项
WinForms 或 WPF 项目里,如果直接在按钮点击事件里写 connection.Open(),界面会卡死。C# 的 SqlConnection.Open() 是同步阻塞调用,而数据库响应时间不可控(网络延迟、锁等待、大结果集等)。
有人试图用 Task.Run(() => { /* 同步查库 */ }) 绕过,这反而增加线程调度开销,且无法取消操作。正确路径是使用原生异步方法:OpenAsync()、ExecuteReaderAsync()、ExecuteScalarAsync()。
实操建议:
- UI 事件处理器必须标记
async,且调用链顶端不能用.Wait()或.Result,否则死锁 - 连接字符串里加
Pooling=false仅用于调试,生产环境必须保持连接池开启(默认就是开启) - EF Core 用户也得懂这个底层逻辑——它的
ToListAsync()底层仍是调用SqlDataReader.ReadAsync()
实际项目里最常被忽略的,是连接字符串中 Connection Timeout 的默认值(15 秒)和连接池的 Max Pool Size(100)。当并发查询突增或某次查询意外卡住,这两个值会成为瓶颈,而不是代码逻辑本身。


















