C# MySQL in multithreaded application - c#

I've developed an emulation program for a game. The program works with MySQL for database communication. I have tried to use the IDisposable method (which is suggested mostly by everyone), to allocate a MySQL connection to a thread, when that specific thread requires something to do with the database. However, when the server reaches over 400 users, the MySQL server gets overloaded with requests. I have also tried the pooling option (size: min 0, max. 150) - it resulted the same issue.
Currently, I am using locking to provide a parallelism system. The system works with static objects (static connection object) to manage the whole server (I think it affects the performance offered by multithreading though).
public static Boolean Query(ref MySqlDataReader Resource, String Query)
{
lock (Connection)
{
MySqlCommand Cmd = new MySqlCommand(Query, Connection);
try
{
Resource = Cmd.ExecuteReader();
}
catch (MySqlException Exception)
{
Console.WriteLine("MySQL Error: " + Exception.Message);
return false;
}
return true;
}
}
A method that works with the "Query" method.
public static String GetObjectProperty(
String Field, String Identifier,
Boolean Primary = true, String Table = "Accounts")
{
String FieldValue = null;
try
{
lock (GeneralDB.Connection)
{
MySqlDataReader DataReader = null;
if (GeneralDB.Query(
ref DataReader,
"SELECT " + MySqlHelper.EscapeString(Field) +
" FROM " + Table +
" WHERE " + (Primary ? "ID" : "Name") + " = '" +
MySqlHelper.EscapeString(Identifier) + "';") == false)
{
return null;
}
if (DataReader == null) return null;
while (DataReader.Read())
FieldValue = DataReader[0].ToString();
DataReader.Close();
return FieldValue;
}
}
catch (Exception Exception)
{
return FieldValue;
}
}
I desperately need some suggestions. Thanks.

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

Update row in Access table using OleDb

I try to update a row in my Access, my code is running fine and I have to Exception, But is nothing change un my database
This is my method it calls from a form in a Winform project
public static void UpdateNextReportNumber(int machineNumber, string reportNumber)
{
try
{
using (OleDbConnection openCon = new OleDbConnection(localConnectionString))
{
string saveStaff = "UPDATE [Calibration] " +
"SET [NextReportNumber]=#report " +
"where [MachineNumber]=#machine";
using (OleDbCommand querySaveStaff = new OleDbCommand(saveStaff))
{
querySaveStaff.Connection = openCon;
querySaveStaff.Parameters.AddWithValue("#machine", 16);
querySaveStaff.Parameters.AddWithValue("#report",2);//Convert.ToInt32(reportNumber.Remove(0, 3)) + 1
openCon.Open();
int recordsAffected = querySaveStaff.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
//WriteLog(ex.StackTrace, ex.Message);
throw ex;
}
}
this is how my Calibration table looks like
my code pass this line
int recordsAffected = querySaveStaff.ExecuteNonQuery();
But in recordsAffected I have value 0
I have no idea what to do
I tried to execute using Access this query
UPDATE [Calibration]
SET [NextReportNumber]=2
where [MachineNumber]=36
And its work fine
I also used
public static void AddCalibration(Calibration calibration)
{
try
{
using (OleDbConnection openCon = new OleDbConnection(localConnectionString))
{
string saveStaff = "INSERT into [Calibration] ([MachineNumber] ,[LastCalibrationDate] ,[NextCalibrationDate])" +
"VALUES (#MachineNumber, #LastCalibrationDate, #NextCalibrationDate)";
using (OleDbCommand querySaveStaff = new OleDbCommand(saveStaff))
{
querySaveStaff.Connection = openCon;
querySaveStaff.Parameters.AddWithValue("#MachineNumber", calibration.MachineNumber);
querySaveStaff.Parameters.AddWithValue("#LastCalibrationDate", calibration.LastCalibrationDate);
querySaveStaff.Parameters.AddWithValue("#NextCalibrationDate", calibration.NextCalibrationDate);
openCon.Open();
int recordsAffected = querySaveStaff.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
//WriteLog(ex.StackTrace, ex.Message);
throw ex;
}
}
And it works fine also...
Thanks for help...
In OleDb parameters are not recognized by their name but by their position in the parameters collection. You should simply change the line order of your parameters
querySaveStaff.Parameters.AddWithValue("#report",2);
querySaveStaff.Parameters.AddWithValue("#machine", 16);
In your current query the report's parameter is used in the Where statement not in the update part and of course nothing is updated because there is no record with WHERE MachineNumber = 2
Indeed, in OleDb you usually specify the parameters placeholder with a single ? not with the #something syntax, but Access, probably for easier portability with Sql Server accepts also the # syntax, still the positions in parameter's collection should be the correct one expected in the query text.

Mysql Exception on ExecuteReader

Well i had a weird exception on my program so i tried to replicate it to show you guys, so what i did was to create a table with id(int-11 primary), title(varchar-255) and generated 100k random titles with 40 chars lenght, when i run my method that reads the count for each id it throws an exception check below for more.
What i found is that this was because of timeouts so i tried this for the timeouts.
set net_write_timeout=99999; set net_read_timeout=99999;
Tried pooling=true on connection
Tried cmd.timeout = 120;
I also tried adding MaxDegreeOfParallelism i played with multiple values but still the same error appears after a while.
My exception:
Could not kill query, aborting connection. Exception was Unable to
read data from the transport connection: A connection attempt failed
because the connected party did not properly respond after a period of
time, or established connection failed because connected host has
failed to respond.
public static string db_main = "Server=" + server + ";Port=" + port + ";Database=" + database_main + ";Uid=" + user + ";Pwd=" + password + ";Pooling=true;";
private void button19_Click(object sender, EventArgs e)
{
List<string> list = db.read_string_list("SELECT id from tablename", db.db_main);
//new ParallelOptions { MaxDegreeOfParallelism = 3 },
Task.Factory.StartNew(() =>
{
Parallel.ForEach(list, id =>
{
string sql = "SELECT COUNT(*) FROM tablename where id=" + id;
var ti = db.read_int(sql, db.db_main);
Console.WriteLine(ti);
});
}).ContinueWith(_ =>
{
Console.WriteLine("Finished");
});
}
public static int? read_int(string sql, string sconn)
{
var rdr = MySqlHelper.ExecuteReader(db.db_main, sql);
if (rdr.HasRows)
{
rdr.Read();
return rdr.GetInt32(0);
}
else
return null;
}
Alternate Method to read int with timeout option.
public static int? read_int2(string sql, string sconn)
{
using (var conn = new MySqlConnection(sconn))
{
using (var cmd = new MySqlCommand(sql, conn))
{
//cmd.CommandTimeout = 120;
conn.Open();
using (var rdr = cmd.ExecuteReader())
{
if (rdr.HasRows)
{
rdr.Read();
return rdr.GetInt32(0);
}
else
return null;
}
}
}
}
What can be causing this? any clues?
So finally my solution on this was to increase net_read_timeout variable (im pointing out net_write_timeout because that can happen when executing a long query too)
Run these queries *Note: After you restart your PC default values will take place again.
set ##global.net_read_timeout = 999;
set ##global.net_write_timeout = 999;
or you can add this on the connection string
default command timeout=999;
Finally i used this method to read the values.
public static int? read_int(string sql, string sconn)
{
try
{
using (MySqlDataReader reader = MySqlHelper.ExecuteReader(sconn, sql))
{
if (reader.HasRows)
{
reader.Read();
return reader.GetInt32(0);
}
else
return null;
}
}
catch (MySqlException ex)
{
//Do your stuff here
throw ex;
}
}

String not formed when trying to create inside a foreach

I have a class People_val. I make a list of this class in another class. Now I do a foreach on this list and for each item I call its ToSQL() func which gives me an insert Query. The list count is more than 50,000 records. The 9774 th query is not fully formed.
9771 INSERT INTO CONVERSION_PEOPLE_VALIDATION VALUES (16413,'Jnto','Johnson', And because of this the command is not executed and transaction is rolled back.
What should i do inorder to get the full string in foreach.
public class People_Val
{
public int MEMBERS_ID;
public string PFirstName;
public string PLastName;
public DateTime? BIRTH_DATE;
public string RELATIONSHP;
public string ToSQL()
{
return string.Format("INSERT INTO CONVERSION_PEOPLE_VALIDATION VALUES ({0},'{1}','{2}','{3}', TO_CHAR('{4}', 'DD-MON-YYYY'))",
MEMBERS_ID, PFirstName.Replace("'","\""), PLastName.Replace("'","\""), RELATIONSHP, BIRTH_DATE);
}
}
public void insertPeopleValTrans(List<People_Val> _lstPeoplevals)
{
int count=0;
TextWriter tw = new StreamWriter(#"C:\PeopleVal.txt");
using (OracleConnection conn = new OracleConnection(connectionStr)) {
conn.Open();
OracleTransaction trans;
trans = conn.BeginTransaction();
try {
foreach (People_Val peopleVal in _lstPeoplevals) {
OracleCommand cmd = new OracleCommand(peopleVal.ToSQL(), conn, trans);
cmd.ExecuteNonQuery();
count++;
if (count % 500 == 0) {
Console.WriteLine("saving " + count.ToString() + " Records");
}
tw.WriteLine(count.ToString()+" "+ peopleVal.ToSQL());
}
trans.Commit();
tw.Close();
} catch (Exception ex) {
Console.WriteLine(ex.Message);
trans.Rollback();
}
conn.Close();
}
}
You probably have an apostrophe in some data which is causing your query to fall over.
Try parametrizing your input... it looks like the RELATIONSHIP field is raw data and that may be where the problem is. Have a look at http://www.devart.com/dotconnect/oracle/articles/parameters.html I'm fairly sure this will sort you out.
Paste in line 9771 let us see it...
Is it really necessary that all 50000 records either succeed or fail together? Otherwise skip the transaction or at least commit a little more often, maybe something like...
if (count % 500 == 0)
{
trans.Commit();
Console.WriteLine("saving " + count.ToString() + " Records");
trans = conn.BeginTransaction();
}
And I agree with the previos comments, this would be a minimal effort using Entity Framework Code First.

Is it possible to modify an MS Access database schema using ADO.NET?

I need to modify the schema of an MS Acess database (.mdb) via code.
Since the Jet Engine DDL statements (ALTER TABLE, etc.) are quite poorly documented, I'd prefer to use some kind of object library like DAO (myDatabase.TableDefs("myTable").Fields.Append(myNewField)) or ADOX (myCatalog.Tables("myTable").Columns.Append(myNewField)) or SMO (which is only available for SQL Server, syntax similar - you get the idea).
Is there something similar like ADOX for ADO.NET or am I stuck with using DDL statements or referencing the old DAO/ADOX libraries?
I have had decent success with straight ddl statements. Your right the syntax requires a smidge of googling to tease out but I have been handling updates to a local db this way for a while. Is there a specific update you are having issues with?
Basically I wrote a few helper functions to check the structure of a table and append fields if needed.
public bool doesFieldExist(string table, string field)
{
bool ret = false;
try
{
if (!openRouteCon())
{
throw new Exception("Could not open Route DB");
}
DataTable tb = new DataTable();
string sql = "select top 1 * from " + table;
OleDbDataAdapter da = new OleDbDataAdapter(sql, routedbcon);
da.Fill(tb);
if (tb.Columns.IndexOf(field) > -1)
{
ret = true;
}
tb.Dispose();
}
catch (Exception ex)
{
log.Debug("Check for field:" + table + "." + field + ex.Message);
}
return ret;
}
public bool checkAndAddColumn(string t, string f, string typ, string def = null)
{
// Update RouteMeta if needed.
if (!doesFieldExist(t, f))
{
string sql;
if (def == null)
{
sql = String.Format("ALTER TABLE {0} ADD COLUMN {1} {2} ", t, f, typ);
}
else
{
sql = String.Format("ALTER TABLE {0} ADD COLUMN {1} {2} DEFAULT {3} ", t, f, typ, def);
}
try
{
if (openRouteCon())
{
OleDbCommand cmd = new OleDbCommand(sql, routedbcon);
cmd.ExecuteNonQuery();
string msg = "Modified :" + t + " added col " + f;
log.Info(msg);
if (def != null)
{
try
{
cmd.CommandText = String.Format("update {0} set {1} = {2}", t, f, def);
cmd.ExecuteNonQuery();
}
catch (Exception e)
{
log.Error("Could not update column to new default" + t + "-" + f + "-" + e.Message);
}
}
return true;
}
}
catch (Exception ex)
{
log.Error("Could not alter RouteDB:" + t + " adding col " + f + "-" + ex.Message);
}
}
else
{
return true;
}
return false;
}

Categories