ExecuteReader? Connection property has not been initialized - c#

I'm trying to do a login form, but when i try to login, I get an exception.
I'm using a separate file called conexaoDB.cs to create the connection with all info about the DB
Outside the button:
ConexaoDB conexao = new ConexaoDB();
Inside the button:
try
{
conexao.conn();
string sql = "SELECT * FROM users WHERE users_login = #login AND users_pass = #senha";
SqlCommand command = new SqlCommand(sql);
command.Parameters.AddWithValue("#login", login);
command.Parameters.AddWithValue("#senha", senha);
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
MessageBox.Show("Popup next window = " + login + " // " + senha);
tryLogin = true;
}
else
{
MessageBox.Show("Login ou senha inválidos.");
tryLogin = false;
}
}
catch (Exception ex)
{
MessageBox.Show("Catch: " + ex.Message);
}
This is only to learn SQL connection, is not a real database with real logins and passwords

Drop the ConexaoDB stuff, especially if you are trying to keep one connection open at all time. That is bad practice, built-in connection pooling will work much better.
This should work:
try
{
string sql = "SELECT * FROM users WHERE users_login = #login AND users_pass = #senha";
using (var connection = new SqlConnection(connectionString)
{
connection.Open();
using (var command = new SqlCommand(sql, connection)
{
command.Parameters.AddWithValue("#login", login);
command.Parameters.AddWithValue("#senha", senha);
using (var reader = command.ExecuteReader())
{
tryLogin = reader.HasRows;
if (tryLogin)
{
// Extremely bad practice to show password in a messagebox, but I assume it's for test
MessageBox.Show("Popup next window = " + login + " // " + senha);
}
else
{
MessageBox.Show("Login ou senha inválidos.");
}
}
}
}
}
catch (SqlException ex) // Don't catch too broad an exception, be specific
{
MessageBox.Show("Catch: " + ex.Message);
}
The usings are taking care of closing and disposing and the SqlConnection goes back to the connection pool and will be reused.
Important:
You should not store passwords as clear text in the database. Best practice is to generate a hash, with a salt and store the hashed value in the database. When authenticating the user you compute the hash again on the password input by the user and compare the two hashes. This assures that your password table is useless to an attacker if it gets stolen.

Related

Request error in C# MySqlCommand, but working in phpMyAdmin

I am trying to make an update on my MySql database, but I don't understand why it doesn't work when executing from MySqlCommand, I have the famous message "check the manual that corresponds to your MySQL server version" (I already have hundreds of queries working correctly, so I guess something is with syntax?).
edit 2 : Here is the part "near " :
'В-513',PRIORITY=1050,QUANTITY_INIT=28,QUANTITY_REMAINING=0,FICHIER='C:\\Actcut3' at line 1'
Here is the update query :
UPDATE launching_order_details SET
ID_LO=1935,
ID_CONTRACT=4228,
ID_PHASE=11765,
ID_ASS=235314,
LIST_REP_ORI='1005817//В-513//235314//В1007//11765//1//30',IS_SUBDETAIL=0,
REF_DETAIL='3201\\1\\В1007\\В-513\\',
NAME='В-513',
PRIORITY=1050,QUANTITY_INIT=28,QUANTITY_REMAINING=0,
FICHIER='C:\\Actcut3.10\\Data\\Parts\\3201\\1\\В1007\\В-513.ini' WHERE ID=27701
Of course I send it in a same line, I just splitted it here for better readability.
If I make a copy/paste of query, then execute it from phpMyadmin, all is working fine.
Edit : C# code :
DBConnect class :
public void Update(string query)
{
if (this.OpenConnection() == true)
{
if (isMySQL)
{
MySqlCommand cmd = new MySqlCommand(query.Replace("[vsteel].", ""), connection);
cmd.ExecuteNonQuery();
this.CloseConnection();
}
else
{
SqlCommand command = new SqlCommand(query, MSconnection);
command.Parameters.Add(new SqlParameter("0", 1));
//command.Connection = this.MSconnection;
command.ExecuteNonQuery();
this.CloseConnection();
}
}
}
public bool OpenConnection()
{
if (isMySQL)
{
try
{
connection.Open();
return true;
}
catch (MySqlException ex)
{
//When handling errors, you can your application's response based
//on the error number.
//The two most common error numbers when connecting are as follows:
//0: Cannot connect to server.
//1045: Invalid user name and/or password.
switch (ex.Number)
{
case 0:
MessageBox.Show("Cannot connect to server. Contact administrator");
break;
case 1045:
MessageBox.Show("Invalid username/password, please try again");
break;
}
System.Windows.Forms.Application.Exit();
Global.is_restarted = true;
return false;
}
}
else
{
try
{
MSconnection.Open();
return true;
}
catch (MySqlException ex)
{
//When handling errors, you can your application's response based
//on the error number.
//The two most common error numbers when connecting are as follows:
//0: Cannot connect to server.
//1045: Invalid user name and/or password.
switch (ex.Number)
{
case 0:
MessageBox.Show("Cannot connect to server. Contact administrator");
break;
case 1045:
MessageBox.Show("Invalid username/password, please try again");
break;
}
System.Windows.Forms.Application.Exit();
Global.is_restarted = true;
return false;
}
}
}
RepereLO class :
private void update()
{
this.listRepereOri = this.listRepereOri.OrderBy(x => x.Priority).ThenBy(x => x.ID).ToList();
DBConnect DataBase = new DBConnect();
string query = "UPDATE [vsteel].launching_order_details SET " +
"ID_LO=" + this.launchingOrder.ID + "," +
"ID_CONTRACT=" + this.contract.ID + "," +
"ID_PHASE=" + this.phase.ID + "," +
"ID_ASS=" + this.assembly.ID + "," +
"LIST_REP_ORI=\'" + convertListRepereOriToString() + "\'," +
"IS_SUBDETAIL=" + Convert.ToInt32(this.isSubRepere) + "," +
"REF_DETAIL=\'" + this.refDetail + "\'," +
"NAME=\'" + this.name + "\'," +
"PRIORITY=" + this.priority + "," +
"QUANTITY_INIT=" + this.quantity + "," +
"QUANTITY_REMAINING=" + this.remainingQuantity + "," +
"FICHIER=\'" + Global.ReplaceSpecialCharacters(this.fileName) + "\' " +
"WHERE ID=" + this.id;
DataBase.Update(query);
}
EDIT 2 : Parametirezed query
my DBConnect class
public void UpdateNew(string query, MySqlParameter[] myParamArray)
{
if (this.OpenConnection() == true)
{
using (MySqlCommand cmd = new MySqlCommand(query.Replace("[vsteel].", ""), connection))
{
for (int i = 0; i < myParamArray.Count(); i++)
{
cmd.Parameters.Add(myParamArray[i]);
}
cmd.Prepare();
cmd.ExecuteNonQuery();
}
}
}
In object :
private void update()
{
this.listRepereOri = this.listRepereOri.OrderBy(x => x.Priority).ThenBy(x => x.ID).ToList();
string query = "UPDATE [vsteel].launching_order_details SET " +
"ID_LO=#idLo," +
"ID_CONTRACT=#idContract," +
"ID_PHASE=#idPhase," +
"ID_ASS=#idAss," +
"LIST_REP_ORI=#listRepOri," +
"IS_SUBDETAIL=#isSubdetail," +
"REF_DETAIL=#refDetail," +
"NAME=#name," +
"PRIORITY=#priority," +
"QUANTITY_INIT=#qtyInit," +
"QUANTITY_REMAINING=#qtyRemaining," +
"FICHIER=#fichier" +
" WHERE ID=#id";
MySqlParameter[] listParams = new MySqlParameter[]
{
new MySqlParameter("id", this.id),
new MySqlParameter("idLo", this.launchingOrder.ID),
new MySqlParameter("idContract", this.Contract.ID),
new MySqlParameter("idPhase", this.Phase.ID),
new MySqlParameter("idAss", this.Assembly.ID),
new MySqlParameter("listRepOri", this.convertListRepereOriToString()),
new MySqlParameter("isSubdetail", this.isSubRepere),
new MySqlParameter("refDetail", this.refDetail),
new MySqlParameter("name", this.name),
new MySqlParameter("priority", this.priority),
new MySqlParameter("qtyInit", this.quantity),
new MySqlParameter("qtyRemaining", this.remainingQuantity),
new MySqlParameter("fichier", this.fileName),
};
DBConnect DataBase = new DBConnect();
DataBase.UpdateNew(query, listParams);
}
The actual problem is using string concatenation to construct a query from external input. This leaves the code wide open to SQL injection, conversion errors (what date format? decimal separator?) and ... syntax errors like this. What if Name is O'Reily for example? Or a user entered ' DROP TABLE Students; # ? No amount of escaping or replacing is going to fix the real bug - using string concatenation.
The correct way to do this is to use parameterized queries. This is actually easier than concatenating strings. If you use a library like Dapper, it's as easy as :
string sql=#"UPDATE [vsteel].launching_order_details
SET
ID_LO=#idlo,
ID_CONTRACT=#contract,
ID_PHASE=#phase,
ID_ASS=#assembly,
LIST_REP_ORI=#ori,
IS_SUBDETAIL=#isSubDetail,
REF_DETAIL=#ref,
NAME=#name,
PRIORITY=#priority,
QUANTITY_INIT=#initial,
QUANTITY_REMAINING=#remaining,
FICHIER=#path,
WHERE ID=#id";
using(var connection=new MySqlConnection(...))
{
connection.Execute(sql, new {
id,
idLo=launchingOrder.ID ,
contract=contract.ID,
....,
path=fileName});
}
Without Dapper, the code is a bit more complex but still easier and safer to write than string concatenation and trying to replace characters.
using(var connection=new MySqlConnection)
using (var cmd=new MySqlCommand(query,connection))
{
cmd.Parameters.AddWithValue("#id",this.id);
...
connection.Open();
cmd.ExecuteNonQuery();
}
BTW the DbConnect class has other issues as well. Long-lived database connections are a bug that harms performance and scalability. The locks taken during a connection remain active until it closes, which results in increased blocking for all clients. This happens even in databases with multi-version concurrency like PostgreSQL.
Connections are meant to be opened as late as possible and closed immediately after use. That's why you see all samples and tutorials create connections in a using block. This ensures the connection is close immediately after use.
ADO.NET uses connection pooling to eliminate the cost of opening a new connection, by reseting existing connections. When DbConnection.Close is called, the connection is reset and placed in a connection pool.
Tutorials
Basics of ADO.NET is a short intro to ADO.NET that explains what the various classes do and how they're used.
MySQL's Tutorial: An Introduction to Connector/NET Programming shows how to use ADO.NET with MySQL.
Microsoft's documentation on ADO.NET is almost an entire book that goes in great depth, so you should probably use it only as a reference
Dapper is a micro-ORM library that makes it very easy to map object properties to parameters and results to objects. It can be used with any ADO.NET provider, including MySQL.
With Dapper, one can write code like this :
public class Dog
{
public int? Age { get; set; }
public Guid Id { get; set; }
public string Name { get; set; }
public float? Weight { get; set; }
public int IgnoredProperty { get { return 1; } }
}
var guid = Guid.NewGuid();
var dog = connection.Query<Dog>("select Age = #Age, Id = #Id", new { Age = (int?)null, Id = guid });
And the library will map the Age and Id properties to #Age and #Id. It will also map the Age and Id columns in the results to Dog.Age and Dog.Id

C# Generating new id from database on windows forms application

I have to make automatic generate new AccountID on my load windows form app.
So for example when users start windows form "Add new Account" in textbox for "Account id" I have to show latest value from database. If i have two accounts in database on windows form in textbox value will be three.
My code perfectly work if i have at least one account in database, but when my database is empty i got exception.
This is my code:
public int GetLatestAccountID()
{
try
{
command.CommandText = "select Max(AccountID)as maxID from Account";
command.CommandType = CommandType.Text;
connection.Open();
OleDbDataReader reader= command.ExecuteReader();
if (reader.Read())
{
int valueID = Convert.ToInt32(reader["maxID"]);
return valueID + 1;
}
return 1;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (connection!= null)
{
connection.Close();
}
}
}
Also I find answer on stackoverflow:
object aa = DBNull.Value;
int valueID = (aa as int?).GetValueOrDefault();
But this line of code works if my database is empty, but when I have one account in the database, it will always show on my windows form in account id textbox value one. I use Microsoft Access 2007 database.
I appreciate any help.
You may further simplify it like below,
Select isnull(max(accountID),0) as maxID from Account
I'm guessing you want:
public int GetLatestAccountID(string connectionString)
{
using(var dbConn = new OleDbConnection(connectionString))
{
dbConn.Open();
string query = "select Max(AccountID) from Account";
using(var dbCommand = new OleDbCommand(query, dbConn))
{
var value = dbCommand.ExecuteScalar();
if ((value != null) && (value != DBNull.Value))
return Convert.ToInt32(value) + 1;
return 1;
}
}
}
It looks like you're opening your database connection once and leaving it open during your entire program. Don't do that; that leads to race conditions and data corruption. .NET implements database connection pooling so you're not improving performance at all by leaving connections open.
You're also not telling us what you're using GetLatestAccountID for. If you're trying to use that as a primary key you are also going to run into problems with race conditions. If you want a primary key you should let the database create it and return the value after you've created the record.
public int GetLatestAccountID()
{
try
{
int accounts = 0;
command.CommandText = "select Max(AccountID)as maxID from Account";
command.CommandType = CommandType.Text;
connection.Open();
OleDbDataReader reader= command.ExecuteReader();
if (reader.Read())
{
accounts = Convert.ToInt32(reader["maxID"]) + 1;
}
return accounts;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (connection!= null)
{
connection.Close();
}
}
}
Could you use SELECT COUNT(column_name) FROM table_name; to count number of accounts instead of selecting which one is the biggest?

How to Search in SQL Server Compact Database in windows form application using C#?

My Code For Searching Data In SQL Server Compact Database is not working please review my code. any help will be greatly appreciated.
#region btnSearch_Click
private void btnSearch_Click(object sender, EventArgs e)
{
SqlCeConnection con = new SqlCeConnection("Data Source="
+ System.IO.Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "Database.sdf"));
sda = new SqlCeDataAdapter();
if (con.State == ConnectionState.Closed)
{
con.Open();
}
string sql = "select Name from tblCustomers ";
if (tbSearch.Text.Length > 0)
{
sql += "where Name like " + tbSearch.Text + " % ";
}
try
{
SqlCeCommand cmd = new SqlCeCommand(sql, con);
cmd.CommandType = CommandType.Text;
// if you don’t set the result set to
// scrollable HasRows does not work
SqlCeResultSet rs = cmd.ExecuteResultSet(
ResultSetOptions.Scrollable);
if (rs.HasRows)
{
int Name = rs.GetOrdinal("Name");
// Hold the output
StringBuilder output = new StringBuilder();
// Read the first record and get it’s data
rs.ReadFirst();
output.AppendLine(rs.GetString(Name)
+ " " + rs.GetString(Name));
while (rs.Read())
{
output.AppendLine(rs.GetString(Name)
+ " " + rs.GetString(Name));
}
// Set the output in the label
lblResults.Text = output.ToString();
}
else
{
lblResults.Text = "No Rows Found.";
}
}
catch (SqlCeException sqlexception)
{
MessageBox.Show(sqlexception.Message, "Error.",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error.",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
con.Close();
}
#endregion
it's throwing the bellow exception.
There was an error parsing the query. [ Token line number = 1,Token line offset = 53,Token in error = % ]
A useful way to solve such issues is to view the SQL string generated by your code right before sending it to SQL Server. If you can spot the problem immediately, that's great - fix it. If you can't try running the full query directly with the SQL Server Management Studio and see if you understand the problem. If you still can't post this query as a question on a Q&A site (just like here on SO) and it will be much easier to help you.
In this case, it looks to me like you're missing single quotes around the value ("like 'text'") - but I can't be sure cause it depends on the value of tbSearch.Text.

Check login credentials from a database

I am storing usernames and passwords in a MySql database. I am using the following code to verify the credentials based on the data from my database for a login. The codes works fine. My question is whether this is bad practice and is there a better way to do this.
My approach is to connect to that database, extract and store those information in a List and compare them to the users input coming from a text box input.
//Extracting information from the database and storing it in a List
public void Login()
{
MySqlCommand cmdReader;
MySqlDataReader myReader;
userQuery = "SELECT * FROM User";
string name = "Name";
string user = "UserName";
string pw = "Password";
string connString = "server=" + server + "; userid=" + userid + "; password=" + password + "; database=" + database;
try
{
conn.ConnectionString = connString;
conn.Open();
cmdReader = new MySqlCommand(userQuery, conn);
myReader = cmdReader.ExecuteReader();
while (myReader.Read())
{
string tempUser, tempPassword;
if (name != null)
{
tempUser = myReader.GetString(user);
tempPassword = myReader.GetString(pw);
users.Add(tempUser);
passwords.Add(tempPassword);
}
}
myReader.Close();
}
catch (Exception err)
{
MessageBox.Show("Not connected to server. \nTry again later.");
Application.Current.Shutdown();
}
}
//Comparing the List data with the users input from textbox1 and textbox2 to verify
private void btn1_Click(object sender, RoutedEventArgs e)
{
for (int x = 0; x < users.Count; x++)
{
for (int y = 0; y < passwords.Count; y++)
{
if (users[x] == textbox1.Text && passwords[y] == textbox2.Text)
{
MessageBox.Show("Login successful");
}
}
}
}
Do not store password in plain text in database, store their hashes. See(Best way to store password in database)
Instead of querying and retrieving all the users, send specific user name and password to database and compare the returned result.
As a side note, do not use string concatenation to form SQL queries, instead use parameters, something like:
using (MySqlCommand cmd = new MySqlCommand("SELECT Count(*) FROM User = #userName AND password = #password"),conn)
{
cmd.Parameters.AddwithValue("#username", username);
cmd.Parameters.AddwithValue("#password", password);
....
var count = cmd.ExecuteScalar(); //and check the returned value
}
Currently you are retrieving all records from User table and then comparing it with the values at client side, imagine if you have a large number of users, brining that much data to client end would not make sense.
Looping on two list and comparing index is not a optimal.
If you want to pre fetch then a Dictionary.
Key lookup on Dictionary is O(1)
Dictionary<string,string> UserIDpw = new Dictionary<string,string>();
while (myReader.Read())
{
UserIDpw.Add(myReader.GetString(0), myReader.GetString(1));
}
But the answer from Habib is a better approach. You don't have a performance issue the requires you to prefetch and prefetch comes with issues. For one you have the passwords on the web server where thy are easier to hack.
I would use a stored procedure for this, and then send username and password as parameters. Depending on whether this is intranet app, or something that is out on the internet, I might do the hash thing as Habib suggests.

Show messages according to the MySQL database data? C#

I want to get the values from MySQL database and that would need to show the messages according to values. But it does not happen and that will always show int privilege is 0. If I did not assign that default value, errors will be showing on the code.
How can I solve this issue and show messages according to the int privilege values?
private void button_login_Click(object sender, RoutedEventArgs e)
{
string username = usernameInput.Text;
string password = passwordInput.Password;
int privilege = 0;
try
{
//This is command class which will handle the query and connection object.
string Query = "SELECT`tbl_user_login`.`u_id`,`tbl_user_login`.`u_username`,
`tbl_user_login`.`u_password`,`tbl_user_login`.`u_privilege`
FROM `bcasdb`.`tbl_user_login`WHERE `tbl_user_login`.`u_username` = '"
+ username + "' AND `tbl_user_login`.`u_password` ='" + password
+ "' AND `tbl_user_login`.`u_privilege` = #privi;";
MySqlConnection conn =
new MySqlConnection(BCASApp.DataModel.DB_CON.connection);
MySqlCommand cmd = new MySqlCommand(Query, conn);
cmd.Parameters.AddWithValue("#privi", privilege);
MySqlDataReader MyReader;
conn.Open();
MyReader = cmd.ExecuteReader();
// Here our query will be executed and data saved into the database.
if (MyReader.HasRows && this.Frame != null)
{
while (MyReader.Read())
{
if (privilege == 1)
{
DisplayMsgBox("click ok to open the admin page ", "OK");
}
if (privilege == 2)
{
DisplayMsgBox("click ok to open the staff page ", "OK");
}
else
{
DisplayMsgBox("privilege 0", "ok");
}
}
}
else
{
DisplayMsgBox("sucess else", "ok");
}
conn.Close();
}
catch (Exception )
{
DisplayMsgBox("sucess catch", "ok");
}
}
Looks like what you're trying to do is checking the value of u_privilege column from tbl_user_login table instead of making a where condition based on privilege. You need to remove this where condition
AND `tbl_user_login`.`u_privilege` = #privi
and also remove the parameter assignment
cmd.Parameters.AddWithValue("#privi", privilege);
You can get the value of tbl_user_login.u_privilege by using MySqlDataReader.GetInt32 syntax inside while (MyReader.Read()) block
MyReader.GetInt32(3)
Please note that 3 is used because MyReader.GetInt32 requires a zero based index parameter and tbl_user_login.u_privilege is the fourth column from your query. The value should be assigned to privilege variable as below
privilege = MyReader.GetInt32(3)
On a side note, you should parameterize your query to avoid SQL injection. Here's the complete code after implementing the above changes
int privilege = 0;
try
{
//This is command class which will handle the query and connection object.
string Query = "SELECT`tbl_user_login`.`u_id`,`tbl_user_login`.`u_username`,
`tbl_user_login`.`u_password`,`tbl_user_login`.`u_privilege`
FROM `bcasdb`.`tbl_user_login`WHERE `tbl_user_login`.`u_username` =
#username AND `tbl_user_login`.`u_password` = #password;";
MySqlConnection conn =
new MySqlConnection(BCASApp.DataModel.DB_CON.connection);
MySqlCommand cmd = new MySqlCommand(Query, conn);
cmd.Parameters.AddWithValue("#username", username);
cmd.Parameters.AddWithValue("#password", password);
MySqlDataReader MyReader;
conn.Open();
MyReader = cmd.ExecuteReader();
// Here our query will be executed and data saved into the database.
if (MyReader.HasRows && this.Frame != null)
{
while (MyReader.Read())
{
privilege = MyReader.GetInt32(3)
if (privilege == 1)
{
DisplayMsgBox("click ok to open the admin page ", "OK");
}
if (privilege == 2)
{
DisplayMsgBox("click ok to open the staff page ", "OK");
}
else
{
DisplayMsgBox("privilege 0", "ok");
}
}
}
else
{
DisplayMsgBox("sucess else", "ok");
}
conn.Close();
}
catch (Exception )
{
DisplayMsgBox("sucess catch", "ok");
}
If im not wrong, the privilege is being returned as a string type. Try take it in as a string then cast it to an integer?

Categories