C# Exception with MySQL UPDATE query - c#

Recently, I have been working on a C# plugin for a game I play. My problem at the moment is that the query listed below is generating many errors, such as these errors:
MySql.Data.MySqlClient.MySqlException: Data too long for column 'lastServer' at row 1
^^This error shows when my lastServer data type is varchar(10), but when I changed it to varchar(25), it went away. However I would still like to know why this would be, if anyone is able to figure it out.
.
My current error is having #lastServer, #characterName, and #ip showing up as #lastServer, #characterName, and #ip instead of their "AddWithValue" values.
Here's the query used for Table Creation:
command.CommandText = string.Concat("CREATE TABLE `", MDiscipline.Instance.Configuration.Instance.PlayerInfoTableName, "` (`steamId` varchar(32) NOT NULL,`characterName` varchar(40) NOT NULL,`ip` varchar(15) NOT NULL, `lastPunishment` tinyint(2) UNSIGNED NOT NULL,`lastServer` varchar(25) NOT NULL,`lastLogin` TIMESTAMP NOT NULL DEFAULT current_timestamp, PRIMARY KEY (`steamId`)) ");
And Here is the Code I'm having problems with:
public void UpdatePlayerInfo(string steamid, string characterName, string ip)
{
try
{
MySqlConnection connection = createConnection();
MySqlCommand command = connection.CreateCommand();
command.Parameters.AddWithValue("#steamId", steamid);
command.Parameters.AddWithValue("#characterName", characterName);
command.Parameters.AddWithValue("#ip", ip);
command.Parameters.AddWithValue("#lastServer", MDiscipline.Instance.Configuration.Instance.Instance);
command.CommandText = "UPDATE `" + MDiscipline.Instance.Configuration.Instance.PlayerInfoTableName + "` SET `characterName` = '#characterName', `ip` = '#ip', `lastServer` = '#lastServer', `lastLogin` = NOW() WHERE `steamId` = '" + steamid.ToString() + "';";
connection.Open();
command.ExecuteNonQuery();
connection.Close();
}
catch (Exception ex)
{
Logger.LogException(ex);
}
}
Helpful info to understand variables:
MDiscipline.Instance.Configuration.Instance.PlayerInfoTableName can be translated to "player_info".
steamid or (#steamId) can be translated to a 32 char number.
characterName or (#characterName) can be translated to any value between 1 and 30 characters.
ip or (#ip) can be translated to an ip address in a string, such as "255.255.255.255".
MDiscipline.Instance.Configuration.Instance.Instance or (#lastServer) can be translated to Server01.

Related

Why is my call to LastInsertedId not returning the expected value?

I have MySql Tables with autoinc ID columns, such as "director_id" here:
CREATE TABLE directors (
director_id Integer NOT NULL AUTO_INCREMENT,
first_name VarChar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
middle_name VarChar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
last_name VarChar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
suffix VarChar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
PRIMARY KEY (
director_id
)
)
I want to store the autoincremented director_id value in the movies_main Table.
So I try to assign the autoincremented value to an int variable:
long director_id = 0;
...in the call to LastInsertedId here (last line):
if (!alreadyExists)
{
comm.Parameters.Clear();
comm.CommandText = "INSERT INTO Directors " +
"(first_name, middle_name, last_name, suffix) " +
"VALUES " +
"(#first_name, #middle_name, #last_name, #suffix)";
comm.Parameters.AddWithValue("#first_name", directorNamePartsList[0]);
comm.Parameters.AddWithValue("#middle_name", directorNamePartsList[1]);
comm.Parameters.AddWithValue("#last_name", directorNamePartsList[2]);
comm.Parameters.AddWithValue("#suffix", directorNamePartsList[3]);
comm.ExecuteNonQuery();
director_id = comm.LastInsertedId;
}
...and then assign it to the movies_main Table like so:
if (!alreadyExists)
{
comm.Parameters.Clear();
comm.CommandText = "INSERT INTO Movies_Main " +
"(movie_title, mpaa_rating, imdb_rating, movie_length, director_id,
screenwriter_id, year_released) " +
"VALUES " +
"(#movie_title, #mpaa_rating, #imdb_rating, #movie_length, #director_id,
#screenwriter_id, #year_released)";
comm.Parameters.AddWithValue("#movie_title", title);
comm.Parameters.AddWithValue("#mpaa_rating", mpaa_rating);
comm.Parameters.AddWithValue("#imdb_rating", Math.Round(imdb_rating, 1));
comm.Parameters.AddWithValue("#movie_length", movie_length);
comm.Parameters.AddWithValue("#director_id", director_id);
comm.Parameters.AddWithValue("#screenwriter_id", screenwriter_id);
comm.Parameters.AddWithValue("#year_released", year_released);
comm.ExecuteNonQuery();
movie_id = comm.LastInsertedId;
}
Yet the value assigned to the movies_main Table for director_id is always 0!
Why is LastInsertId (apparently) returning 0, and how can I get it to actually return the value its name claims it does? Will I have to resort to a "SELECT MAX(director_id)" query to actually get the value?
NOTE: The movie_id code does work! I get a non-zero value when assigning the result of the call to LastInsertedId to the movie_id variable, and it is added to other tables just fine. This code works as expected:
foreach (var gen_desc in genreList)
{
long genreID = Convert.ToInt32(GetGenreIDForDescription(gen_desc));
alreadyExists = PairAlreadyExistsInMoviesGenresM2Mtable(
movie_id, genreID);
if (!alreadyExists)
{
comm.Parameters.Clear();
comm.CommandText = "INSERT INTO Movies_Genres " +
"(movie_id, genre_id) " +
"VALUES " +
"(#movie_id, #genre_id)";
comm.Parameters.AddWithValue("#movie_id", movie_id);
comm.Parameters.AddWithValue("#genre_id", genreID);
comm.ExecuteNonQuery();
}
}
An alternative way to LastInsertedId property from the MySqlCommand is the native MySql function LAST_INSERT_ID. We can call this function and get its return value adding a simple SELECT statement to your current command text. MySql supports batch statements and so, with a single server call we could execute more than one single command text.
if (!alreadyExists)
{
comm.Parameters.Clear();
comm.CommandText = "INSERT INTO Directors " +
"(first_name, middle_name, last_name, suffix) " +
"VALUES " +
"(#first_name, #middle_name, #last_name, #suffix); " + // semicolon to close the first statement
"SELECT LAST_INSERT_ID()";
comm.Parameters.AddWithValue("#first_name", directorNamePartsList[0]);
comm.Parameters.AddWithValue("#middle_name", directorNamePartsList[1]);
comm.Parameters.AddWithValue("#last_name", directorNamePartsList[2]);
comm.Parameters.AddWithValue("#suffix", directorNamePartsList[3]);
director_id = Convert.ToInt64(comm.ExecuteScalar());
}
Note that we can now use ExecuteScalar because we get back just one record with a single column.
Let me say however that I have tried to reproduce your problem with LastInsertedId. I have recreated your table and written a simple script in LinqPad trying to insert some fixed data in that table.
I have no problem with LastInsertedId property and I get the correct value. I have read that if you have more threads that are concurrently inserting records you could get some problems with that property but I have no proof of any kind of misbehaving

How to get the autoincremented ID inserted row using SQLiteCommand object

I have a SQLite Connection Object and a command object. I insert a row in my table using the ExecuteNonQuery function. How do I get the value of the autoincrement column (ID) from this?
Code for creating database:
creationQuery = "CREATE TABLE IF NOT EXISTS MyTable ( ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT ,MyCol1 NVARCHAR, MyCol2 NVARCHAR)";
My code for inserting values in the DB:
public void InsertIntoDB(string[] vals){
// Global connection objects (This is in an API so every time a new instance of these are created)
connObj = CreateConnection();
cmdObj = connObj.CreateCommand();
cmdObj.CommandText = "INSERT INTO MyTable ('MyCol1',MyCol2) VALUES( '" + vals[0] + "','" + vals[1] + "')";
int id = -1;
try{
cmdObj.ExecuteNonQuery();
id = (int)cmdObj.Parameters["id"].Value; // tried "#id" as well
}catch(Exception ex){
throw ex;
}
}
This code is inserting correctly. But throws an exception ( System.ArgumentOutOfRangeException) in the line where I'm trying to get the ID. Whats going on/ How do i solve this?
EDIT 1: Inside the try block, I added code to just run another query "Select max(ID) from MyTable":
try
{
cmdObj.ExecuteNonQuery();
cmdObj.CommandText = "Select Max(id) from MyTable";
SQLiteDataReader myReader = cmdObj.ExecuteReader();
while (myReader.Read())
{
id = (int)myReader["id"];
}
Console.WriteLine(id);
}
This code throws the same Exception.
select last_insert_rowid();
And you will need to execute it as a scalar query.
string sql = #"select last_insert_rowid()";
long lastId = (long)command.ExecuteScalar(sql); // Need to type-cast since `ExecuteScalar` returns an object.

Create a MySQL sproc from c#, delimiter issues

I'm trying to create stored procedures from a c# program. It typically reads the sproc definition from a text file, and then run it against the chosen database.
My SQL script file looks like this:
DROP PROCEDURE IF EXISTS MySproc;
DELIMITER //
CREATE PROCEDURE MySproc(
IN Id BIGINT,
IN Reference VARCHAR(255),
IN Bla VARCHAR(255)
)
BEGIN
INSERT INTO TableA(`Id`, `Reference`) VALUES(Id, Reference);
INSERT INTO TableB(`Id`, `Bla`) VALUES(Id, Bla);
END
//
DELIMITER ;
and this works fine in the workbench.
I then execute it with this type of c# code:
using (MySqlCommand sqlCommand = _mySqlConnection.CreateCommand())
{
sqlCommand.Connection = _mySqlConnection;
sqlCommand.CommandText = scriptfile;
sqlCommand.CommandType = CommandType.Text;
sqlCommand.ExecuteNonQuery();
}
And it errors with:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DELIMITER //
CREATE PROCEDURE MySproc( IN Id BIGINT
' at line 1
If I remove the DELIMITER // stuff, then it still parses the semi colons between BEGIN and END as a delimiter for the outer statement, and it errors with:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'END' at line 31
Any idea how I can set either the Command or the something int he script so that it works? Not even sure if the error actually comes from MySQL itself, or from the MySqlCommand library (MySql.Data.6.9.9). And MySQL server is 5.6.25, InnoDB tables.
Can you try replacing the DELIMITER // with something like delimiter $$
Pointless question, sorry, it's embarrassing. This morning, I created a unit test following the top part of this page: https://dev.mysql.com/doc/connector-net/en/connector-net-programming-stored-using.html
and it works fine:
[TestMethod]
public void TestSprocCreationFromMySqlDoc()
{
// from https://dev.mysql.com/doc/connector-net/en/connector-net-programming-stored-using.html
MySqlConnection conn = new MySqlConnection();
conn.ConnectionString = "server=localhost;user=root;database=test;port=3306;password=;";
MySqlCommand cmd = new MySqlCommand();
try
{
Console.WriteLine("Connecting to MySQL...");
conn.Open();
cmd.Connection = conn;
cmd.CommandText = "DROP PROCEDURE IF EXISTS add_emp";
cmd.ExecuteNonQuery();
cmd.CommandText = "DROP TABLE IF EXISTS emp";
cmd.ExecuteNonQuery();
cmd.CommandText = "CREATE TABLE emp (empno INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(20), last_name VARCHAR(20), birthdate DATE)";
cmd.ExecuteNonQuery();
cmd.CommandText = "CREATE PROCEDURE add_emp(" +
"IN fname VARCHAR(20), IN lname VARCHAR(20), IN bday DATETIME, OUT empno INT)" +
"BEGIN INSERT INTO emp(first_name, last_name, birthdate) " +
"VALUES(fname, lname, DATE(bday)); SET empno = LAST_INSERT_ID(); END";
cmd.ExecuteNonQuery();
}
finally
{
conn.Close();
}
}
Then, I did another test with my own statements (trying to replicate file read from disk, with crlf) and it works too!
cmd.CommandText =
"DROP PROCEDURE IF EXISTS MySproc; " + Environment.NewLine +
"CREATE PROCEDURE MySproc(" + Environment.NewLine +
"IN Id BIGINT," + Environment.NewLine +
"IN Reference VARCHAR(255)," + Environment.NewLine +
"IN Bla VARCHAR(255))" + Environment.NewLine +
"BEGIN " + Environment.NewLine +
"INSERT INTO TableA(`Id`, `Reference`) VALUES(Id, Reference); " + Environment.NewLine +
"INSERT INTO TableB(`Id`, `Bla`) VALUES(Id, Bla); " + Environment.NewLine +
"END";
cmd.ExecuteNonQuery();
And then I ran my original application (that read scripts from files) and it runs ok as well! So I can't explain it. I'm wondering if I did something to the MySql server that affected all connections, or did something in one connection that stayed on, affecting all connections, until a reboot.

Executing SqlCommand fails due to "string or binary data would be truncated. the statement has been terminated" error

I am trying to save changes made in a GridView of DevExpress into a table of my DataBase, and I was able to do it with the following code:
using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings[2].ConnectionString))
{
con.Open();
GridView gv = sender as GridView;
using (SqlCommand cmd = con.CreateCommand())
{
cmd.CommandText = "UPDATE dbo.tb_Alumno set " + e.Column.FieldName + " = '" + e.Value + "' where pk_alumno = " + gv.GetRowCellValue(gv.FocusedRowHandle, gv.Columns[0]);
cmd.ExecuteNonQuery();
}
}
I have been having this problem since I added parametric command to prevent SQL injection. I have already hardcoded the values for each parameter to encounter the one that provides the error and is the #val parameter:
using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings[2].ConnectionString))
{
con.Open();
GridView gv = sender as GridView;
using (SqlCommand cmd = con.CreateCommand())
{
#region Parameters
//cmd.CommandText = "UPDATE dbo.tb_Alumno set " + e.Column.FieldName + " = '" + e.Value + "' where pk_alumno = " + gv.GetRowCellValue(gv.FocusedRowHandle, gv.Columns[0]);
cmd.CommandText = "UPDATE dbo.tb_Alumno set #col = #val where pk_alumno = #id";
cmd.Parameters.AddWithValue("#col", e.Column.FieldName);
cmd.Parameters.AddWithValue("#val", e.Value);
//cmd.Parameters["#val"].SqlDbType = SqlDbType.VarChar;
cmd.Parameters.AddWithValue("#id", gv.GetRowCellValue(gv.FocusedRowHandle, gv.Columns[0]));
#endregion
try
{ cmd.ExecuteNonQuery(); }
catch (Exception xe)
{ MessageBox.Show(xe.Message); }
}
}
Aditional information that I can provide:
The error comes when executing the query
I am using a gridview from DevExpress15.1 in the 'CellValueChanged' event
Data type of the Database is varchar(50) and the parameter is nvarchar
The error comes when the string exceeds 6 characters (even being "hardcoded")
For example, if I use the following parameter it will throw the same error:
cmd.Parameters.AddWithValue("#col", "name");
cmd.Parameters.AddWithValue("#val", "More than 6 characters");
cmd.Parameters.AddWithValue("#id", 1);
This happens with all fields
If I change the middle line to the following I do not have problems at all
cmd.Parameters.AddWithValue("#val", "This<7");
I already tryed to match datatypes as you could see from the commented line, but that throws me no error and does not update the table. I have tested this on other columns too and have the same basic problem.
So my question is, ¿What piece of code should I use to prevent SQL inyection in this particular case?
Here's an example of using a stored procedure that uses dynamic sql. Notice the dynamic sql utilizes sp_executesql which helps prevent sql injection attacks.
The SQL code
create PROCEDURE [dbo].[SP_DynamicUpdate]
-- Add the parameters for the stored procedure here
#col varchar(50) = null,
#val varchar(50) = null,
#id int = 0
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
-- declaring strings
DECLARE #SQLString nvarchar(1024);
DECLARE #ParmDefinition nvarchar(512)
SET #SQLString =
N'Update
dbo.tb_Alumno
set
#col_Param = #val_Param
where
pk_alumno = #id_Param'
-- creating the parameters
SET #ParmDefinition = N'#col_Param varchar(50), ';
SET #ParmDefinition = #ParmDefinition + N'#val_Param varchar(50), ';
SET #ParmDefinition = #ParmDefinition + N'#id_Param int ';
-- Executing the stored procedure
EXECUTE sp_executesql #SQLString,
#ParmDefinition,
#col_Param = #col,
#val_Param = #val,
#id_Param = #id
END
the C# code
using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings[2].ConnectionString))
{
GridView gv = sender as GridView;
using (SqlCommand cmd = new SqlCommand("dbo.SP_DynamicUpdate", con))
{
#region Parameters
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#col", e.Column.FieldName);
cmd.Parameters.AddWithValue("#val", e.Value);
cmd.Parameters.AddWithValue("#id", gv.GetRowCellValue(gv.FocusedRowHandle, gv.Columns[0]));
#endregion
try
{
con.Open();
cmd.ExecuteNonQuery();
}
catch (Exception xe)
{
MessageBox.Show(xe.Message);
}
}
}
This will add more security and allow you to test the stored procedure separately with different parameters. You can even add more checks in the stored procedure call - like checks for column names exist.
In terms of sql injection attacks in the way you're doing it, there's not much more you can do.
I was able to find the simplest way to solve my issue and was as follows:
I failed to notice that when I created the bound between the datagrid and the database a tableAdapter and dataSet was automatically created. With this link as reference I found that adapter had an update method, so I used the information so I simply used this code and it worked like a charm, I also added the line to validate the field wich, to be honest I think is not needed.
Validate(); //Or this.Validate();
tb_AlumnoTableAdapter.Update(escuelaDataSet.tb_Alumno);
As before, I put this code under the 'CellValueChanged' event but I assume I can atach it to a button to make it a save button. Thanks to everyone for your answers and I hope this helps other like me.
You may also like to read: How to: Update Data by Using a TableAdapter.

Execute a Query After Call a stored procedure

This is my stored procedure
CREATE DEFINER=`root`#`localhost` PROCEDURE `sp_CPC`(
IN _B VARCHAR(100),
IN _G VARCHAR(2),
IN _R VARCHAR(30),
IN _D VARCHAR(30),
OUT _C FLOAT,
OUT _P FLOAT)
BEGIN
//Something Hear
END$$
DELIMITER ;
I Call this stored procedure by C# flowing Code
DataSet tmpDataSet = new DataSet();
mCommand.CommandText = "sp_CPC";
mCommand.CommandType = CommandType.StoredProcedure;
// mCommand.CommandText = "sp_select_all_employees";
mCommand.Parameters.AddWithValue("#_B", "bty-23");
mCommand.Parameters.AddWithValue("#_G", "3");
mCommand.Parameters.AddWithValue("#_R", "9000");
mCommand.Parameters.AddWithValue("#_D", "92");
mCommand.Parameters.Add("#_C",MySqlDbType.Float);
mCommand.Parameters["#_C"].Direction = ParameterDirection.Output;
mCommand.Parameters.Add("#_P", MySqlDbType.Float);
mCommand.Parameters["#_P"].Direction = ParameterDirection.Output;
try
{
mConnection.Open();
mCommand.ExecuteNonQuery();
mAdapter.Fill(tmpDataSet)
}
catch (Exception ex)
{
strErrorInfo = ex.ToString();
}
finally
{
mConnection.Close();
}
DataTable dtb = tmpDataSet.Tables[0];
mCommand.CommandText = "INSERT INTO abc (xxxx,yyyy) VALUES ('" + dtb.Rows[0][0] + "','" + dtb.Rows[0][1] + "')";
mConnection.Open();
mCommand.ExecuteNonQuery();
mConnection.Close();
return tmpDataSet;
it show error in this command mCommand.ExecuteNonQuery();
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '-14' AND name LIKE '5378377032052','6'' at line 1.
(Return tmpDataSet) use Because of this data i also use anouther work
The way you construct the INSERT statement is unsafe. You concatenate strings that might contain ' characters, so that your INSERT statement becomes invalid - and also prone to SQL injection attacks. The error message you show in the comments points in this direction.
In order to solve this, use parameters in the INSERT statement as you did in your stored procedure.
Sample:
// ...
mCommand.CommandText = "INSERT INTO abc (xxxx,yyyy) VALUES (#val1, #val2)";
mCommand.Parameters.AddWithValue("#val1", dtb.Rows[0][0]);
mCommand.Parameters.AddWithValue("#val2", dtb.Rows[0][1]);
// ...

Categories

Resources