C#中MySqlDataReader.HasRows始终返回false,即使表中有数据
嘿,我来帮你排查这个问题!你遇到的HasRows一直返回false的情况,大概率是这几个原因导致的,咱们一步步来理清楚:
可能的问题根源
1. 重复调用Read()的小坑(但不是核心原因)
你代码里连续调用了两次reader.Read():
reader.Read(); Console.WriteLine(reader.Read());
第一次Read()会把阅读器的指针移到第一行数据(如果有的话),第二次Read()会尝试跳到下一行——如果你的查询结果只有一行,这步就会返回false。不过要说明的是,HasRows是判断结果集有没有行,和当前指针位置无关,所以这不是HasRows为false的根本原因,但会导致你后面读取reader["username"]的时候直接报错(因为指针已经跑到结果集外面了)。
2. SQL字符串拼接的致命问题(最可能的元凶)
你用字符串拼接写SQL的方式太危险了:
String sql = "SELECT * FROM user WHERE username='" + username + "' AND password='" + password + "'";
如果username或者加密后的password里有单引号、反斜杠这类特殊字符,直接就会把SQL语句搞坏。比如用户名是O'Neil的话,拼接后的SQL就变成了:
SELECT * FROM user WHERE username='O'Neil' AND password='xxx'
这明显是语法错误,数据库执行的时候直接返回空结果集,HasRows自然就是false了。而且这种写法还会招来SQL注入攻击,绝对不能在生产环境用!
3. MD5加密的一致性问题
你用md5Crypt(password)加密密码,但要确保数据库里存的密码和你加密后的结果完全匹配:
- 检查加密算法的大小写:比如你加密后是小写,数据库里存的是大写?
- 检查字符编码:加密时用的是UTF-8还是GBK?和数据库存储的编码要一致。
- 检查空格:输入的密码前后有没有空格?加密后就会和数据库里的不一样。
解决办法,按优先级来
第一步:立刻换成参数化查询(必须做)
这能同时解决特殊字符和SQL注入的问题,代码改成这样:
password = md5Crypt(password); User u = null; // 用占位符代替直接拼接 String sql = "SELECT * FROM user WHERE username=@username AND password=@password"; try { conn.Open(); MySqlCommand cmd = new MySqlCommand(sql, conn); // 给参数赋值 cmd.Parameters.AddWithValue("@username", username); cmd.Parameters.AddWithValue("@password", password); MySqlDataReader reader = cmd.ExecuteReader(); // 先判断HasRows再读取,或者直接用Read()的返回值 if (reader.HasRows) { reader.Read(); // 移到第一行数据 u = new User(reader["username"].ToString(), reader["password"].ToString(), reader["name"].ToString(), reader["role"].ToString()); } else { u = null; } reader.Close(); // 别忘了关闭阅读器 conn.Close(); } catch(Exception ex) { // 加上异常打印,方便排查问题 Console.WriteLine("查询出错:" + ex.Message); // 确保连接关闭,避免资源泄漏 if(conn.State == ConnectionState.Open) conn.Close(); }
第二步:排查MD5加密的匹配问题
- 在代码里打印加密后的
password值,然后去数据库客户端(比如Navicat、MySQL Workbench)执行这条查询:
SELECT * FROM user WHERE username='你的测试用户名' AND password='打印出来的加密密码'
如果查不到数据,说明你的加密逻辑和数据库里存的密码不匹配,得调整加密代码。
2. 检查数据库的password字段类型:MD5加密后是32位字符,所以字段至少要设为VARCHAR(32),别设短了。
第三步:修复Read()的调用逻辑
别连续调用两次Read(),要么先判断HasRows再调用Read(),要么直接用Read()的返回值判断:
// 更简洁的写法:直接用Read()的返回值判断是否有数据 if(reader.Read()){ u = new User(reader["username"].ToString(), reader["password"].ToString(), reader["name"].ToString(), reader["role"].ToString()); } else { u = null; }
额外的调试小技巧
在代码里打印参数的值(比如加密后的密码、用户名),然后直接在数据库客户端执行查询,看能不能拿到结果。如果客户端能查到,代码里查不到,那就是代码里的参数传递或者加密的问题;如果客户端也查不到,那就是数据库里的数据确实不匹配,或者SQL逻辑有问题。
内容的提问来源于stack exchange,提问作者yuyu

