C# MySQLDataReader returns 列名而不是字段值

C# MySQLDataReader returns column names instead of field values

我正在使用带有本地数据库的 MySQLClient。我写了一个方法,其中 returns 一个关于用户的数据列表,我在其中指定我想要数据的列,它动态生成查询。

但是,reader 只返回列名而不是实际数据,我不知道为什么,因为以前在用户登录时,相同的方法在程序中起作用。

我正在使用参数化查询来防止 SQL 注入。

这是我的代码。我已经删除了与问题无关的部分,但如果需要我可以提供完整的代码。

namespace Library_application
{
    class MainProgram
    {
        public static Int32 user_id;

        static void Main()
        {
            MySqlConnection conn = LoginProgram.Start();
            //this is the login process and works perfectly fine so i won't show its code
            if (conn != null)
            {
                //this is where things start to break
                NewUser(conn);
            }
            Console.ReadLine();
        }

        static void NewUser(MySqlConnection conn)
        {
            //three types of users, currently only using student
            string query = "SELECT user_role FROM Users WHERE user_id=@user_id";
            Dictionary<string, string> vars = new Dictionary<string, string>
            {
                ["@user_id"] = user_id.ToString()
            };
            MySqlDataReader reader = SQLControler.SqlQuery(conn, query, vars, 0);

            if (reader.Read())
            {
                string user_role = reader["user_role"].ToString();
                reader.Close();

                //this works fine and it correctly identifies the role and creates a student
                Student user = new Student(conn, user_id);
                //later i will add the logic to detect and create the other users but i just need this to work first
            }
            else
            {
                throw new Exception($"no user_role for user_id - {user_id}");
            }

        }

    }

    class SQLControler
    {
        public static MySqlDataReader SqlQuery(MySqlConnection conn, string query, Dictionary<string, string> vars, int type)
        {
            MySqlCommand cmd = new MySqlCommand(query, conn);
            int count = vars.Count();
            MySqlParameter[] param = new MySqlParameter[count];
            //adds the parameters to the command
            for (int i = 0; i < count; i++)
            {
                string key = vars.ElementAt(i).Key;
                param[i] = new MySqlParameter(key, vars[key]);
                cmd.Parameters.Add(param[i]);
            }
            //runs this one
            if (type == 0)
            {
                Console.WriteLine("------------------------------------");
                return cmd.ExecuteReader();
                //returns the reader so i can get the data later and keep this reusable
            }

            else if (type == 1)
            {
                cmd.ExecuteNonQuery();
                return null;
            }
            else
            {
                throw new Exception("incorrect type value");
            }

        }

    }

    class User
    {
        public List<string> GetValues(MySqlConnection conn, List<string> vals, int user_id)
        {

            Dictionary<string, string> vars = new Dictionary<string, string> { };
            //------------------------------------------------------------------------------------
            //this section is generating the query and parameters 
            //using parameters to protect against sql injection, i know that it ins't essential in this scenario
            //but it will be later, so if i fix it by simply removing the parameterisation then im just kicking the problem down the road
            string args = "";
            for (int i = 0; i < vals.Count(); i++)
            {
                args = args + "@" + vals[i];
                vars.Add("@" + vals[i], vals[i]);
                if ((i + 1) != vals.Count())
                {
                    args = args + ", ";
                }
            }
            string query = "SELECT " + args + " FROM Users WHERE user_id = @user_id"; 
            Console.WriteLine(query);
            vars.Add("@user_id", user_id.ToString());
            //-------------------------------------------------------------------------------------



            //sends the connection, query, parameters, and query type (0 means i use a reader (select), 1 means i use non query (delete etc..))
            MySqlDataReader reader = SQLControler.SqlQuery(conn, query, vars, 0);

            List<string> return_vals = new List<string>();
            if (reader.Read())
            {
                //loops through the reader and adds the value to list
                for (int i = 0; i < vals.Count(); i++)
                {
                    //vals is a list of column names in the ame order they will be returned
                    //i think this is where it's breaking but im not certain
                    return_vals.Add(reader[vals[i]].ToString());
                }

                reader.Close();
                return return_vals;

            }
            else
            {
                throw new Exception("no data");
            }

        }

    }


    class Student : User
    {

        public Student(MySqlConnection conn, int user_id)
        {
            Console.WriteLine("student created");
            //list of the data i want to retrieve from the db
            //must be the column names
            List<string> vals = new List<string> { "user_forename", "user_surname", "user_role", "user_status"};
            //should return a list with the values in the specified columns from the user with the matching id
            List<string> return_vals = base.GetValues(conn, vals, user_id);

            //for some reason i am getting back the column names rather than the values in the fields
            foreach(var v in return_vals)
            {
                Console.WriteLine(v);
            }

        }

    }

我尝试过的: - 使用获取字符串 - 使用索引而不是列名 - 指定特定的列名 - 使用 while (reader.Read) - 请求不同数量的列

我在登录部分使用了这个方法,它在那里工作得很好(下面的代码)。我不明白为什么它在这里(上面的代码)也不起作用。

    static Boolean Login(MySqlConnection conn)
    {

        Console.Write("Username:   ");
        string username = Console.ReadLine();
        Console.Write("Password:   ");
        string password = Console.ReadLine();

        string query = "SELECT user_id, username, password FROM Users WHERE username=@username";
        Dictionary<string, string>  vars = new Dictionary<string, string>
        {
            ["@username"] = username
        };
        MySqlDataReader reader = SQLControler.SqlQuery(conn, query, vars, 0);

        Boolean valid_login = ValidLogin(reader, password);


        return (valid_login);
    }
    static Boolean ValidLogin(MySqlDataReader reader, string password)
    {
        Boolean return_val;
        if (reader.Read())
        {
            //currently just returns the password as is, I will implement the hashing later
            password = PasswordHash(password);

            if (password == reader["password"].ToString())
            {
                MainProgram.user_id = Convert.ToInt32(reader["user_id"]);
                return_val = true;
            }
            else
            {
                return_val = false;
            }
        }
        else
        {
            return_val = false;
        }
        reader.Close();
        return return_val;

    }

问题出在这里:

string args = "";
for (int i = 0; i < vals.Count(); i++)
{
    args = args + "@" + vals[i];
    vars.Add("@" + vals[i], vals[i]);
    // ...
}
string query = "SELECT " + args + " FROM Users WHERE user_id = @user_id"; 

这将构建一个如下所示的查询:

SELECT @user_forename, @user_surname, @user_role, @user_status FROM Users WHERE user_id = @user_id;

与此同时,vars.Add("@" + vals[i], vals[i]); 最终在查询的 MySqlParameterCollection 中将 @user_forename 映射到 "user_forename"。您的查询最终会为数据库中的每一行选择这些参数的(常数)值。

解决方法是:

  1. 不要在您选择的列名前添加 @
  2. 不要将列名作为变量添加到查询中。

您可以将整个循环替换为:

string args = string.Join(", ", vals);