0

I am using MySQLClient with a local database. I wrote a method which returns a list of data about the user, where I specify the columns I want the data from and it generates the query dynamically.

However, the reader is only returning the column names rather than the actual data and I don't know why, since the same method works previously in the program when the user is logging in.

I am using parameterised queries to protect from SQL injection.

Here is my code. I have removed parts which are unrelated to the problem, but i can give full code if needed.

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);
            }

        }

    }

What i have tried: - Using getstring - Using index rather than column names - Specifying a specific column name - Using while (reader.Read) - Requesting different number of columns

I have used this method during the login section and it works perfectly there (code below). I can't figure out why it doesnt work here (code above) aswell.

    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;

    }
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
aidan byrne
  • 524
  • 1
  • 5
  • 11
  • You are getting only the column names because the query isn't finding any data that matches the filters (WHERE). Try removing the WHERE to see if you get all the data. Then check the filter to see if any rows of the data matches the filter. Often issue like this occur because you are connecting to a different version of the database. So lets make sure the data is in the database before investigating other issues. – jdweng Mar 05 '20 at 12:20
  • i removed the where clause and the same thing happened, just got the column names back, i also tried submitting the query directly into the sql and got the correct results. im sure im connecting to the correct database etc... since im using the same connection as i did for the login and that works fine – aidan byrne Mar 05 '20 at 13:05
  • The default database could be wrong or the credentials may not be correct. You should get same results in c# as a direct query. – jdweng Mar 05 '20 at 13:23
  • but if thats the issue then surely it would have come up in the first part of the program, but that works perfectly – aidan byrne Mar 05 '20 at 13:31
  • update- i switched the select... to select * (manually not parameterised) and that worked, so it has to be something to do with the parameters not being recognised or somethign along those lines – aidan byrne Mar 05 '20 at 13:39
  • You are correct. But then why are you getting nothing without the Where. You should be getting the entire table. Either the previous code is changing something, using wrong table, or wrong columns. – jdweng Mar 05 '20 at 13:40
  • Thanks for posting your full code. I strongly recommend learning how to use a debugger, stepping through your code, and examining the data it's creating. This will show you where the problem lies. – Bradley Grainger Mar 05 '20 at 14:47

1 Answers1

0

The problem is here:

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"; 

This builds a query that looks like:

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

Meanwhile, vars.Add("@" + vals[i], vals[i]); ends up mapping @user_forename to "user_forename" in the MySqlParameterCollection for the query. Your query ends up selecting the (constant) value of those parameters for each row in the database.

The solution is:

  1. Don't prepend @ to the column names you're selecting.
  2. Don't add the column names as variables to the query.

You can do this by replacing that whole loop with:

string args = string.Join(", ", vals);
Bradley Grainger
  • 27,458
  • 4
  • 91
  • 108