I am using MySql 5.6x with Visual Studio 2015, windows 10, 64-bit. C# as programming language. In my CRUD.cs (Class file) i have created the following method:
public bool dbQuery(string sql,string[] paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null){
foreach(string i in paramList){
string[] valus = i.Split(',');
string p = valus[0];
string v = valus[1];
cmd.Parameters[p].Value = v;
}
}
if (cmd.ExecuteNonQuery() > 0)
{
flag = true;
}
}
catch (Exception exc)
{
error(exc);
}
}
I am passing the query and Parameters List like this:
protected void loginBtn_Click(object sender, EventArgs e)
{
string sql = "SELECT * FROM dept_login WHERE (user_email = ?user_email OR user_cell = ?user_cell) AND userkey = ?userkey";
string[] param = new string[] {
"?user_email,"+ userid.Text.ToString(),
"?user_cell,"+ userid.Text.ToString(),
"?userkey,"+ userkey.Text.ToString()
};
if (db.dbQuery(sql, param))
{
msg.Text = "Ok";
}
else
{
msg.Text = "<strong class='text-danger'>Authentication Failed</strong>";
}
}
Now the problem is that after the loop iteration complete, it directly jumps to the catch() Block and generate an Exception that:
Parameter '?user_email' not found in the collection.
Am i doing this correct to send params like that? is there any other way to do the same?
Thanks
EDIT: I think the best way might be the two-dimensional array to collect the parameters and their values and loop then within the method to fetch the parameters in cmd.AddWidthValues()? I may be wrong...
In your dbQuery you don't create the parameters collection with the expected names, so you get the error when you try to set a value for a parameter that doesn't exist
public bool dbQuery(string sql,string[] paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null){
foreach(string i in paramList){
string[] valus = i.Split(',');
string p = valus[0];
string v = valus[1];
cmd.Parameters.AddWithValue(p, v);
}
}
if (cmd.ExecuteNonQuery() > 0)
flag = true;
}
catch (Exception exc)
{
error(exc);
}
}
Of course this will add every parameter with a datatype equals to a string and thus is very prone to errors if your datatable columns are not of string type
A better approach would be this one
List<MySqlParameter> parameters = new List<MySqlParameter>()
{
{new MySqlParameter()
{
ParameterName = "?user_mail",
MySqlDbType= MySqlDbType.VarChar,
Value = userid.Text
},
{new MySqlParameter()
{
ParameterName = "?user_cell",
MySqlDbType= MySqlDbType.VarChar,
Value = userid.Text
},
{new MySqlParameter()
{
ParameterName = "?userkey",
MySqlDbType = MySqlDbType.VarChar,
Value = userkey.Text
},
}
if (db.dbQuery(sql, parameters))
....
and in dbQuery receive the list adding it to the parameters collection
public bool dbQuery(string sql, List<MySqlParameter> paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null)
cmd.Parameters.AddRange(paramList.ToArray());
if (cmd.ExecuteNonQuery() > 0)
{
flag = true;
}
}
catch (Exception exc)
{
error(exc);
}
}
By the way, unrelated to your actual problem, but your code doesn't seem to close and dispose the connection. This will lead to very nasty problems to diagnose and fix. Try to use the using statement and avoid a global connection variable
EDIT
As you have noticed the ExecuteNonQuery doesn't work with a SELECT statement, you need to use ExecuteReader and check if you get some return value
using(MySqlDataReader reader = cmd.ExecuteReader())
{
flag = reader.HasRows;
}
This, of course, means that you will get troubles when you want to insert, update or delete record where instead you need the ExecuteNonQuery. Creating a general purpose function to handle different kind of query is very difficult and doesn't worth the work and debug required. Better use some kind of well know ORM software like EntityFramework or Dapper.
Your SQL Commands' Parameters collection does not contain those parameters, so you cannot index them in this manner:
cmd.Parameters[p].Value = v;
You need to add them to the Commands' Parameters collection in this manner: cmd.Parameters.AddWithValue(p, v);.
Related
How do I store the results from a mysql query for use in other classes most efficiently?
I've tried the following code, which executes properly and stores all data in reader as it should. Reading the DataReader here works fine if I want to!
public class DatabaseHandler
{
public void MySqlGetUserByName(string input_username, MySqlDataReader reader)
{
try
{
_database.Open();
string query = "SELECT * FROM users WHERE username = '#input'";
MySqlParameter param = new MySqlParameter(); param.ParameterName = "#input"; param.Value = input_username;
MySqlCommand command = new MySqlCommand(query, _database);
command.Parameters.Add(param);
reader = command.ExecuteReader();
_database.Close();
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
But when I try to read the same DataReader here, it is null and throws an exception (right after Debug6).
public class LoginHandler
{
public static void UserAuth(Client user, string input_username, string input_password)
{
DatabaseHandler dataBase = new DatabaseHandler();
MySqlDataReader dataReader = null;
dataBase.MySqlGetUserByName(input_username, dataReader);
Console.WriteLine("Debug6");
if (!dataReader.HasRows)
{
user.SendChatMessage("No match found.");
return;
}
while (dataReader.Read())
{
user.SetData("ID", (int)dataReader[0]);
user.SetData("username", (string)dataReader[1]);
user.SetData("email", (string)dataReader[2]);
user.SetData("password", (string)dataReader[3]);
}
dataReader.Close();
}
}
Please let me know how to make this work, or if there is a more efficient way of doing this without limiting the function of MySqlGetUserByName. The purpose of it is to input a name and a place to store all info from the match in the database.
Also, feel free to drop in any other suggestions that could make the code more efficient.
You could change your MySqlGetUserByName to return a User instance if all goes well, otherwise you return a null instance to the caller (Or you can thrown an exception, or you can set a global error flag in the DatabaseHandler class..., but to keep things simple I choose to return a null)
public class DatabaseHandler
{
public User MySqlGetUserByName(string input_username)
{
User result = null;
try
{
string query = "SELECT * FROM users WHERE username = #input";
using(MySqlConnection cnn = new MySqlConnection(......))
using(MySqlCommand command = new MySqlCommand(query, cnn))
{
cnn.Open();
command.Parameters.AddWithValue("#input", input_username);
using(MySqlDataReader dataReader = command.ExecuteReader())
{
if (dataReader.Read())
{
result = new User();
result.ID = Convert.ToInt32(dataReader[0]);
..... and so on with the other user properties ....
}
}
}
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
// Return the user to the caller. If we have not found the user we return null
return result;
}
}
In the same way the caller handles the situation
public class LoginHandler
{
public static void UserAuth(string input_username, string input_password)
{
DatabaseHandler dataBase = new DatabaseHandler();
User result = dataBase.MySqlGetUserByName(input_username);
// If we have not found the user we have a null in the variable
if(result == null)
{
// Send your message using a static method in the user class
// User.SendMessage("User with username {input_username} not found!");
}
else
{
// User ok. return it? or do something with its data?
}
}
}
I've a problem and I can't figured it out how to solve it.
I've a class for fetching data from a Database, in this class I've a method for a simple select * this method is called
List<T> All<T>(string tableName)
and you have to specify which resource you want to fetch, for example
All<User>("users")
And, aside from the classic SQL Reader and SQL Command, the core of the method is this
public override List<T> All<T>(string resource)
{
List<T> result = new List<T>();
using (MySqlConnection sqlConnection = new MySqlConnection(connectionString))
{
sqlConnection.Open();
try
{
string query = "SELECT * FROM " + resource + " WHERE 1=1";
using (MySqlCommand sqlCommand = new MySqlCommand(query, sqlConnection))
{
lock (locker)
{
MySqlDataReader reader = sqlCommand.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
T model = Activator.CreateInstance<T>();
Dictionary<string, object> _properties = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
string property = reader.GetName(i);
object value = reader.GetValue(i);
_properties.Add(property, value);
}
var type = model.GetType();
var method = type.GetMethod("SetProperties");
var invoked = method.Invoke(model, new object[] { _properties });
result.Add(model);
}
}
reader.Close();
}
}
}
catch (Exception ex)
{
Program.eventLogger.Add(new Event(EventType.Error, "SQL Data Providers", "Exception catched on All", ex));
}
finally
{
sqlConnection.Close();
}
}
return result;
}
Basically, based on the Type from the method header, the method will try to create an new instance of the specific type, later for each field from the query, it will fills all the attributes of the class on a temporaneous list. Once it's done it will try to call the method "SetProperties" which basically set every attributes of the class using reflection.
This is the core of SetProperties, equal for each entity:
public virtual bool SetProperties(Dictionary<string,object> properties)
{
if(this.ValidateData(properties))
{
FillNullableAttributes(properties);
// Iterate trough every key : value pairs in properties
foreach (KeyValuePair<string, object> kvp in properties)
{
if (this.data.Contains(kvp.Key))
{
var property = this.GetType().GetProperty(kvp.Key);
PropertyInfo propertyInfo = this.GetType().GetProperty(kvp.Key);
// Set the current fetched key with the given value if !null
if (kvp.Value != null)
{
Type fetchedType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
object safeConversion = (kvp.Value == null || kvp.Value == DBNull.Value) ? null : Convert.ChangeType(kvp.Value, fetchedType);
if (propertyInfo.CanWrite)
{
propertyInfo.SetValue(this, safeConversion, null);
}
}
}
}
return true;
}
return false;
}
In conclusion the result, which is a list, will be returned and the specific Entity will have its own BindingList filled. The binding list, for each entity is described as follow:
public static BindingList<Seller> items = new BindingList<Seller>();
This code works fine, even if there's a lot of space for improvements I know, but if I called it twice like this:
User.items = new BindingList<User>(provider.All<User>("users"));
User.items = new BindingList<User>(provider.All<User>("users"));
The second list will be filled by empty entities, the counting of the will be correct but they will be empties... and that shouldn't occurs.
The only thing that I figured it out, from the debugging, is that on the second call
var invoked = method.Invoke(model, new object[] { _properties });
invoked is set to false.
The result of:
var invoked = method.Invoke(model, new object[] { _properties });
Is the return value from your SetProperties method, not whether the method was invoked as your question indicates. Your SetProperties method is telling you that it was unable to do its work, debug that and you will find your answer.
I designed my webpage to read a data string then display the results on labels in an html table. I am attempting to highlight the row that my database reads as a current order. My only problem is only one record is set to be active but they all highlight as if they were active. I use an array to set my data and I also use the label to get the ID I need (all is in code below). I have posted my method and where I use it in the asp page load. How can I fix my method to return correctly?
The implementing of the method in page load
if (lineData.IsCurrentOrderFind(L68.Text))
{
myTable.Rows[1].Cells[0].BgColor = "#FE2E2E";
myTable.Rows[1].Cells[1].BgColor = "#FE2E2E";
myTable.Rows[1].Cells[2].BgColor = "#FE2E2E";
myTable.Rows[1].Cells[3].BgColor = "#FE2E2E";
myTable.Rows[1].Cells[4].BgColor = "#FE2E2E";
}
Here is method that label above gets passed to
public bool IsCurrentOrderFind(string itemNumber)
{
StringBuilder sqlString = new StringBuilder();
sqlString.Append("SELECT * ");
sqlString.Append("FROM WorkOrder ");
sqlString.Append("WHERE LineNumber = " + ConfigurationManager.AppSettings["Line"] + " AND LineCompleted = 0 AND (ScaleGroup LIKE '%1' OR ScaleGroup LIKE '%3') ");
sqlString.Append(" AND CaseGenNum6 = #CaseGenNum6");
SqlDataReader reader = null;
SqlConnection dbConn = App_Code.DBHelper.getConnection();
SqlParameter[] parameters = new SqlParameter[] { new SqlParameter("#CaseGenNum6", itemNumber) };
try
{
reader = App_Code.DBHelper.executeQuery(dbConn, sqlString.ToString(), parameters);
while (reader.Read())
{
IsCurrentOrder = (reader["IsCurrentOrder"] != DBNull.Value && !string.IsNullOrEmpty(reader["IsCurrentOrder"].ToString())) ? true : false;
}
reader.Close();
reader.Dispose();
dbConn.Close();
dbConn.Dispose();
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (dbConn != null)
{
try { dbConn.Close(); dbConn.Dispose(); }
catch { }
}
if (reader != null)
{
try { reader.Close(); reader.Dispose(); }
catch { }
}
}
if (IsCurrentOrder == true) I realize this is not necessary
{
return true;
}
else
{
return false;
}
}
The problem could be with this expression:
!string.IsNullOrEmpty(reader["IsCurrentOrder"].ToString())
Instead of calling ToString(), try simply casting it to a string:
!string.IsNullOrEmpty((string)reader["IsCurrentOrder"])
Possibly even better (the previous line might throw an exception if it's not really a string):
!string.IsNullOrEmpty(reader["IsCurrentOrder"] as string)
The reason being is that if the string is really null, calling ToString() will return a non-null string "null".
IsCurrentOrder is not declared locally. It seems to be declared at a higher scope. When you enter this function, nothing is initializing the variable (back to false). So, it is remaining at its last setting. Try this code instead:
public bool IsCurrentOrderFind(string itemNumber)
{
bool IsCurrentOrder = false;
//and the rest of your source code
the line
IsCurrentOrder = (reader["IsCurrentOrder"] != DBNull.Value && !string.IsNullOrEmpty(reader["IsCurrentOrder"].ToString())) ? true : false;
}
It's not actually checking the value of the field, only that it's not null or empty.
Try
if(
(reader["IsCurrentOrder"] != DBNull.Value
&&
!string.IsNullOrEmpty(reader["IsCurrentOrder"].ToString()))
)
{
IsCurrentOrder = reader["IsCurrentOrder"];
}
else
IsCurrentOrder = false;
I think there is a lot of refactoring you could do to this method though that will simplify the logic.
Alright I originally started out using a Convert.ToInt32(myradTextBox.Text) then it said specified cast is not valid. I did some research on here and decided to try Int.TryParse. Upon doing so I still received this error. What I am trying to do is when the user enters an ID and hits the create button, it searches the DB to see if that ID is already there. I have also tried to convert the bool value from my Int.TryParse to int using Convert.ToInt32(Result) still same error (see below in third code post for where that would be posted). Maybe it has something to do with my comparison method.
Below I have provided the Int.TryParse method with values. The Method I am calling to check the userinput is not in the db currently and my if statement that is catching the statement. Any input on how to fix this would be greatly appreciated. I am still new to most of this stuff so I apologize if leaving any critical info off. Just ask if you need clarification or something elaborated.
Here is my method for comparison:
public bool isValidID(int id)
{
SqlConnection dbConn = null;
int count = 0;
try
{
using (dbConn = new SqlConnection(Properties.Settings.Default["tville"].ToString()))
{
string sql = "SELECT Count(*) FROM PackLabelFormat where PackFormatID = #PackFormatID";
SqlCommand cmd = dbConn.CreateCommand();
cmd.CommandText = sql;
cmd.Parameters.AddWithValue("#PackFormatID", id);
dbConn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
reader.Read();
count = reader.GetInt16(0);
}
}
}
catch (Exception ex)
{
throw ex;
}
if (count > 0)
return false;
return true;
}
Here is my variables that I use in my Int.TryParse method:
string IDselect = rTxtBoxFormatID.Text.ToString();
int resultInt;
bool result = int.TryParse(IDselect, out resultInt);
Lastly here is my method that is catching the error:
SqlConnection dbConn = null;
LabelData labelList = new LabelData();
try
{
using (dbConn = new SqlConnection(Properties.Settings.Default["tville"].ToString()))
{
if (SelectedVersion.isValidID(resultInt))
{
SelectedVersion.PackFormatID = resultInt;
}
else
{
MessageBox.Show("ID already in use!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
catch (Exception ex)
{
throw ex;
}
The database column did not support Int16 aka short. Which was why my specified cast is not valid error never went away no matter what I tried. Thank you for your help in this matter! Here is the code to further illustrate what the problem was.
using (SqlDataReader reader = cmd.ExecuteReader())
{
reader.Read();
//count = reader.GetInt16(0); needs to be reader.GetInt32(0);
}
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
This SqlCe code looks awfully strange to me:
cmd.CommandText = "INSERT INTO departments ( account_id, name) VALUES (?, ?)";
foreach(DataTable tab in dset.Tables)
{
if (tab.TableName == "Departments")
{
foreach(DataRow row in tab.Rows)
{
Department Dept = new Department();
if (!ret)
ret = true;
foreach(DataColumn column in tab.Columns)
{
if (column.ColumnName == "AccountID")
{
Dept.AccountID = (string) row[column];
}
else if (column.ColumnName == "Name")
{
if (!row.IsNull(column))
Dept.AccountName = (string) row[column];
else
Dept.AccountName = "";
}
}
List.List.Add(Dept);
. . .
dSQL = "INSERT INTO departments ( account_id, name) VALUES ('" + Dept.AccountID + "','" + Dept.AccountName +"')";
if (!First)
{
cmd.Parameters[0].Value = Dept.AccountID;
cmd.Parameters[1].Value = Dept.AccountName;
}
if (First)
{
cmd.Parameters.Add("#account_id",Dept.AccountID);
cmd.Parameters.Add("name",Dept.AccountName);
cmd.Prepare();
First = false;
}
if (frmCentral.CancelFetchInvDataInProgress)
{
ret = false;
return ret;
}
try
{
dbconn.DBCommand( cmd, dSQL, true );
}
. . .
public void DBCommand(SqlCeCommand cmd, string dynSQL, bool Silent)
{
SqlCeTransaction trans = GetConnection().BeginTransaction();
cmd.Transaction = trans;
try
{
cmd.ExecuteNonQuery();
trans.Commit();
}
catch (Exception ex)
{
try
{
trans.Rollback();
}
catch (SqlCeException)
{
// Handle possible exception here
}
MessageBox.Show("DBCommand Except 2"); // This one I haven't seen...
WriteDBCommandException(dynSQL, ex, Silent);
}
}
My questions are:
1) Should "?" really be used in the assignment to cmd.CommandText, or should "#" be used instead?
2) One of the "cmd.Parameters.Add()"s (account_id) uses a "#" and the other (name) doesn't. Which way is right, or is the "#" optional?
3) I can't make heads or tails of why DBCommand() is written as it is - the final two args are only used if there's an exception...???
I'm tempted to radically refactor this code, because it seems so bizarre, but since I don't really understand it, that might be a recipe for disaster...
I'm fairly certain this article will answer some of your questions:
http://msdn.microsoft.com/en-us/library/yy6y35y8.aspx
The second chart explains the difference between the named and positional (?) parameters (used in OleDb and ODBC).
I believe in the case where the ? is used, the # is optional, but I'm not sure of this. If it's working, I'd say that that IS the case.
The stuff in DBCommand appears to simply be there for logging purposes. If the exection fails, it tries to do a rollback and then logs the exception with the sql command (in dynSQL).
The ? parameter is older Access syntax.
My guess is this used to be an Access database, but someone converted it to SQL CE at some point.
Generally, SQL understands that ? parameter, but it's better to just change that while you are in there so that it is more understood.
I'm still trying to make heads & tails of all these variables. If I get it sorted out, I'll post up compilable (sp?) code.
EDIT: I had to put this into a method and work out all of the RED errors to make sure I wasn't giving you something that would not compile.
I passed it your DataSet like so, with lots of comments added:
private bool StrangeSqlCeCode(DataSet dset) {
const string ACCOUNT_ID = "AccountID";
const string DEPARTMENTS = "Departments";
const string NAME = "Name";
const string SQL_TEXT = "INSERT INTO departments (account_id, name) VALUES (#account_id, #name)";
bool ret = false;
//bool First = false; (we don't need this anymore, because we initialize the SqlCeCommand correctly up front)
using (SqlCeCommand cmd = new SqlCeCommand(SQL_TEXT)) {
// Be sure to set this to the data type of the database and size field
cmd.Parameters.Add("#account_id", SqlDbType.NVarChar, 100);
cmd.Parameters.Add("#name", SqlDbType.NVarChar, 100);
if (-1 < dset.Tables.IndexOf(DEPARTMENTS)) {
DataTable tab = dset.Tables[DEPARTMENTS];
foreach (DataRow row in tab.Rows) {
// Check this much earlier. No need in doing all the rest if a Cancel has been requested
if (!frmCentral.CancelFetchInvDataInProgress) {
Department Dept = new Department();
if (!ret)
ret = true;
// Wow! Long way about getting the data below:
//foreach (DataColumn column in tab.Columns) {
// if (column.ColumnName == "AccountID") {
// Dept.AccountID = (string)row[column];
// } else if (column.ColumnName == "Name") {
// Dept.AccountName = !row.IsNull(column) ? row[column].ToString() : String.Empty;
// }
//}
if (-1 < tab.Columns.IndexOf(ACCOUNT_ID)) {
Dept.AccountID = row[ACCOUNT_ID].ToString();
}
if (-1 < tab.Columns.IndexOf(NAME)) {
Dept.AccountName = row[NAME].ToString();
}
List.List.Add(Dept);
// This statement below is logically the same as cmd.CommandText, so just don't use it
//string dSQL = "INSERT INTO departments ( account_id, name) VALUES ('" + Dept.AccountID + "','" + Dept.AccountName + "')";
cmd.Parameters["#account_id"].Value = Dept.AccountID;
cmd.Parameters["#name"].Value = Dept.AccountName;
cmd.Prepare(); // I really don't ever use this. Is it necessary? Perhaps.
// This whole routine below is already in a Try/Catch, so this one isn't necessary
//try {
dbconn.DBCommand(cmd, true);
//} catch {
//}
} else {
ret = false;
return ret;
}
}
}
}
return ret;
}
I wrote an overload for your DBCommand method to work with Legacy code:
public void DBCommand(SqlCeCommand cmd, string dynSQL, bool Silent) {
cmd.CommandText = dynSQL;
DBCommand(cmd, Silent);
}
public void DBCommand(SqlCeCommand cmd, bool Silent) {
string dynSQL = cmd.CommandText;
SqlCeTransaction trans = GetConnection().BeginTransaction();
cmd.Transaction = trans;
try {
cmd.ExecuteNonQuery();
trans.Commit();
} catch (Exception ex) {
try {
trans.Rollback(); // I was under the impression you never needed to call this.
// If Commit is never called, the transaction is automatically rolled back.
} catch (SqlCeException) {
// Handle possible exception here
}
MessageBox.Show("DBCommand Except 2"); // This one I haven't seen...
//WriteDBCommandException(dynSQL, ex, Silent);
}
}