SQL Server中SQL语句有结果,但SqlDataReader读取返回空集求助
看起来你遇到了个头疼的问题:在SQL Server里手动执行查询能拿到2条索引记录,但用C#的SqlDataReader却读不到任何数据。我来帮你拆解几个最可能的原因和对应的解决办法:
1. 数据库账号权限不一致
你在SSMS里用的账号大概率有管理员级别的权限,能顺利访问sys.tables、sys.indexes这类系统视图,但代码里用的VenturaDBUser可能没被授予足够的权限,导致查询返回空结果。
给该用户补充权限即可,执行以下SQL:
-- 授予查看系统对象定义的权限 GRANT VIEW ANY DEFINITION TO VenturaDBUser;
如果只想精细化授权,也可以单独给每个系统视图授权:
GRANT VIEW DEFINITION ON OBJECT::sys.tables TO VenturaDBUser; GRANT VIEW DEFINITION ON OBJECT::sys.indexes TO VenturaDBUser; GRANT VIEW DEFINITION ON OBJECT::sys.index_columns TO VenturaDBUser; GRANT VIEW DEFINITION ON OBJECT::sys.columns TO VenturaDBUser; GRANT VIEW DEFINITION ON OBJECT::sys.schemas TO VenturaDBUser;
2. 字符串拼接引发的SQL问题(含SQL注入风险)
你现在用字符串拼接的方式构建SQL语句,这不仅有严重的SQL注入风险,还可能因为参数的大小写、隐形字符等问题导致查询不匹配。比如如果数据库排序规则是区分大小写的,而传入的tableName/indexName大小写和实际对象不一致,就会返回空结果。
强烈建议改成参数化查询,既安全又能彻底避免拼接错误:
public static TableIndex GetIndex(string indexName, string tableName) { TableIndex index = null; var sql = @"select t.object_id, s.name as schemaname, t.name as tablename, i.index_id, i.name as indexname, index_column_id, c.name as columnname from sys.tables t inner join sys.schemas s on t.schema_id = s.schema_id inner join sys.indexes i on i.object_id = t.object_id inner join sys.index_columns ic on ic.object_id = t.object_id and ic.index_id = i.index_id inner join sys.columns c on c.object_id = t.object_id and ic.column_id = c.column_id where i.index_id > 0 and i.type in (1, 2) and i.is_primary_key = 0 and i.is_unique_constraint = 0 and i.is_disabled = 0 and i.is_hypothetical = 0 and ic.key_ordinal > 0 and t.name = @TableName and i.name = @IndexName"; using (var conn = new SqlConnection("Server=localhost;Database=VenturaERD;User Id=VenturaDBUser;Password=Ventura;")) { conn.Open(); using (var cmd = new SqlCommand(sql, conn)) { // 添加参数,彻底避免拼接错误和SQL注入 cmd.Parameters.AddWithValue("@TableName", tableName); cmd.Parameters.AddWithValue("@IndexName", indexName); using (var reader = cmd.ExecuteReader()) { if (reader.HasRows) { index = new TableIndex { Columns = new List<IndexColumn>() }; while (reader.Read()) { // 仅在第一次读取时初始化基础属性 if (index.TableId == 0) { index.TableId = reader.GetInt32(reader.GetOrdinal("object_id")); index.TableName = reader.GetString(reader.GetOrdinal("tablename")); index.IndexId = reader.GetInt32(reader.GetOrdinal("index_id")); index.IndexName = reader.GetString(reader.GetOrdinal("indexname")); } index.Columns.Add(new IndexColumn() { ColumnName = reader.GetString(reader.GetOrdinal("columnname")), Order = reader.GetInt32(reader.GetOrdinal("index_column_id")) }); } } } } } return index; }
另外我把LIKE改成了=,因为你不需要通配符模糊匹配,精确匹配更准确也更高效。
3. 验证实际执行的SQL语句
你可以在代码里加一行打印语句,把最终执行的SQL输出出来,然后复制到SSMS里用VenturaDBUser账号执行,看是否返回结果:
// 在cmd.ExecuteReader()之前添加 Console.WriteLine(cmd.CommandText);
如果在SSMS里用该账号执行也返回0条,那说明要么是权限还没配置好,要么是传入的参数值和实际数据库对象不匹配(比如拼写错误、大小写不一致)。
4. 读取逻辑的小优化
原来的代码先调用reader.Read()读取第一条,再用while(reader.Read())读取剩余记录,逻辑没问题但可以更简洁。改成用reader.HasRows判断后,在循环里统一处理基础属性和列数据,代码可读性更强,也不容易出错。
内容的提问来源于stack exchange,提问作者Nico Haegens

