I have a DataGridView which displays a list of students and their info in a table.
One column named "Grade" is editable so I wish to reflect all changes to the database when the user clicks the "Save Changes" button.
I've wrote this code but for some reason it doesn't work:
private void bttnStudentsSaveChanges_Click(object sender, EventArgs e)
{
try
{
if (connection.State == ConnectionState.Closed) connection.Open();
DataTable changes = ((DataView)dataGridViewStudents.DataSource).Table.GetChanges();
if (changes != null)
{
foreach (DataRow row in changes.Rows)
{
MySqlCommand updateCommand = connection.CreateCommand();
updateCommand.CommandText = #"UPDATE grades
INNER JOIN lectures ON grades.idLecture = lectures.id
INNER JOIN students ON grades.idStudent = students.id
SET grades.grade = '" + row["Grade"] + #"'
WHERE students.id = '" + row["ID"] +
#"' AND (students.name = '" + row["Name"] +
#"' AND students.surname = '" + row["Surname"] +
"') AND lectures.name = '" + row["Lecture"] + "'";
updateCommand.ExecuteNonQuery();
}
}
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
When this executes there are no changes made to the database.
Since I have to join multiple tables I can't use MySqlCommandBuilder (at least i think that's the reason since I've tried it and got an error about it) so i decided to do it manually.
I've put a couple of breakpoints to check if the data is right and both the changes variable and the CommandText property contain valid data.
I've tested the MySQL query in SQLyog and it worked there as it should
It's as if the updateCommand.ExecuteNonQuery(); doesn't execute.
EDIT: I've added a few things but it still isn't working. The new code is
private void bttnStudentsSaveChanges_Click(object sender, EventArgs e)
{
try
{
connection.Open();
DataTable changes = ((DataView)dataGridViewStudents.DataSource).Table.GetChanges();
if (changes != null)
{
foreach (DataRow row in changes.Rows)
{
MySqlCommand updateCommand = connection.CreateCommand();
updateCommand.CommandText = #"UPDATE grades
INNER JOIN lectures ON grades.idLecture = lectures.id
INNER JOIN students ON grades.IDStudent = students.ID
SET grades.grade = #grade
WHERE students.ID = #ID AND (students.name = #name AND students.surname = #surname)
AND lectures.name = #lecture";
updateCommand.Parameters.AddWithValue("#grade", row["Grade"]);
updateCommand.Parameters.AddWithValue("#ID", row["ID"]);
updateCommand.Parameters.AddWithValue("#name", row["Name"]);
updateCommand.Parameters.AddWithValue("#surname", row["Surname"]);
updateCommand.Parameters.AddWithValue("#lecture", row["Lecture"]);
updateCommand.ExecuteNonQuery();
}
}
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
finally
{
if(connection.State == ConnectionState.Open) connection.Close();
}
}
EDIT2: I found out that ExecuteNonQuery returns a number of affected rows. I'm getting 0 as a result. Which is odd since the command when executed in SQLyog yields 1 row affected as a result. Weird
EDIT3: I found out what the issue is. The database contains Croatian letters (čćšđž) so when they are in a command the command doesn't execute. I think it's something to do with character encoding. When the command only contains regular(ASCII) letters then it works normally. I'm not sure how to fix it yet but at least now i know where the issue lies
EDIT4: Issue resolved. Changed the database collation to utf8
if you have integers in the database type you don't need to give single quotes for the parameters.
try without singles quotes for number types.
You can avoid all these issues and more safer way is by using parameterized query
EDIT
when you get row values you can get it based on type as below
row.Field<int>("ID")
change your code something like below, check the types again
updateCommand.Parameters.AddWithValue("#grade", row.Field<int>("Grade"));
updateCommand.Parameters.AddWithValue("#ID", row.Field<int>("ID"));
updateCommand.Parameters.AddWithValue("#name", row.Field<string>("Name"));
updateCommand.Parameters.AddWithValue("#surname", row.Field<string>("Surname"));
updateCommand.Parameters.AddWithValue("#lecture", row.Field<string>("Lecture");
This code is dangerously fragile. Picking up some connection and not being sure if it is even open is the sign of not really know what's going on. If you don't even know if you have a connection open then you probably don't know whether it's engaged in a transaction. I agree with aquaraga - it probably is but you're just so entangled you cannot tell. There could be another bug elsewhere in the code that doesn't commit.
In any case I'd suggest introducing a design pattern to centralise when you open a connection, create a transaction then do the work then commit or rollback. Doing it in UI events is a pretty bad practice to say the least. At least try to separate your UI from your business and data logic.
Related
the code is below and the error starting from sqlCommand cmd the 13th line of this code
private void button2_Click(object sender, EventArgs e)
{
if (StudenUsn.Text == "" )
{
MessageBox.Show("Enter The Student Number");
} else {
Con.Open();
String query = "update Student_tbl set StdName='" + StudName.Text + "',where FatherName='" + FtName.Text + "',where MotherName='" + MtName.Text + "',where StdAddress='" + Address.Text + "',where Collage ='" + Collage.Text + "'set StdRoom = " + StRmNum.SelectedValue.ToString()+",StdStatus = '"+ StudSt.SelectedItem.ToString() + "' where StdUsn ='"+StudenUsn+ "')";
SqlCommand cmd = new SqlCommand(query, Con);
cmd.ExecuteNonQuery();
MessageBox.Show("Room Successfully Updates");
Con.Close();
FillStudentDGV();
}
}
Your code should look more like:
private void button2_Click(object sender, EventArgs e)
{
if (StudenUsn.Text == "" )
{
MessageBox.Show("Enter The Student Number");
} else {
var query = #"
update Student_tbl
set
StdName=#sn,
FatherName=#fn,
MotherName=#mn,
StdAddress=#sa,
Collage=#c,
StdRoom=#sr,
StdStatus=#ss
where
StdUsn=#su";
using var con = new SqlConnection(YOUR_CONN_STR_HERE);
using var cmd = new SqlCommand(query, con);
cmd.Parameters.AddWithValue(#sn, StudName.Text);
cmd.Parameters.AddWithValue(#fn, FtName.Text);
cmd.Parameters.AddWithValue(#mn, MtName.Text);
cmd.Parameters.AddWithValue(#sa, Address.Text);
cmd.Parameters.AddWithValue(#c, Collage.Text);
cmd.Parameters.AddWithValue(#sr, StRmNum.SelectedValue);
cmd.Parameters.AddWithValue(#ss, StudSt.SelectedItem);
cmd.Parameters.AddWithValue(#su, StudenUsn);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
MessageBox.Show("Room Successfully Updates");
FillStudentDGV();
}
}
There are good reasons to avoid using AddWithValue if you use SQLServer which you can get into at a later date if you want, but it's convenient for me (who doesn't know the types and widths of your columns) dealing with the current massive elephant in the room which is your SQL is massively vulnerable to a hacking technique known as sql injection (and to a lesser extent it would blow up with an error for any student whose name included an apostrophe) - using AddWithValue might make your query slightly slower, but better that than it be the cause of the next data breach; learn how to write SQLs right, right now
Never ever take data supplied by a user and concatenate it into an SQL string. Doing so essentially, in most cases, gives the user access to your database. So many big companies whose developers should know better, put up expensive firewalls and security and then let anyone in via this back door anyway; sql injection prone systems are one of the leading causes of hacks in the world today
Always use #parameter placeholders in the SQL for user data and add a parameter to the command's parameters collection, containing the data
Now on the topic of your actual error; the pattern for an update is
update table
set col1=#param1, col2=#param2 ...
where (some conditions)
You have one where and one set. If there is some conditional aspect to your set, like you only want to update the student name/address if it is currently null then you can do like:
update table
set
name=case when name is null then #n else name end,
address=case when address is null then #a else address end
where (some conditions)
Or more simply
update table
set
name=coalesce(name, #n)
address=coalesce(address, #a)
where (some conditions)
You can't mix n match and say "where this=that where this2=that2 set this3=that3" - that's a syntax error. Where is for picking the row you want to update and set is for starting a block of commas separated columns and values the row data is to be updated to.
Strive to write your sql nicely formatted inside an #string; it's a programming language all of its own, and will be easier to debug if it's laid out nicely
Can u try with it ?
String query = "update Student_tbl set StdName='" + StudName.Text + "',StdRoom = '" + StRmNum.SelectedValue.ToString()+"',StdStatus = '"+ StudSt.SelectedItem.ToString() + "' where FatherName='" + FtName.Text + "' and MotherName='" + MtName.Text + "' and StdAddress='" + Address.Text + "' and Collage ='" + Collage.Text + "' and StdUsn ='"+StudenUsn+ "'";
i got stack with im doing, i have a query that select the minimum value of my quantity, the code running fine for first query B value has a result which is correct from the record, my problem is the second OleQuery the debugging stop in OledDBDatareader and jump into another event. can someone figure our what is the problem?
MyConN.Open();
OleDbCommand OlCmd = new OleDbCommand("Select min(Cqty) from stocks", MyConN);
OleDbDataReader OdR = OlCmd.ExecuteReader();
if (OdR.Read())
{
string B = OdR[0].ToString();
if (B == "")
{}
else
{
string ItemDisc;
string OleQuery = "select *from stocks where Cqty='" + B + "'";
OleDbCommand OlCmdQuery =new OleDbCommand(OleQuery, MyConN);
OleDbDataReader DrQuery = OlCmdQuery.ExecuteReader();
while(DrQuery.Read())
{
ItemDisc = (DrQuery["ItemDesc"].ToString());
}
DrQuery.Close();
DialogResult Result1 = MessageBox.Show("The System detects " + ItemDisc + " product with less than 10 quantity remaining " + Environment.NewLine + " Please check with the suppliers and request orders", "System Message", MessageBoxButtons.OKCancel, MessageBoxIcon.Information);
if (Result1==DialogResult.OK)
{
//some message here
}
}
MyConN.Close();}
Edit: After checking the record in my database and found out that the value is numeric, in my second query i only remove the single quote and the query response as i want.
I think your problem is that you use simultaneously two active dataReaders and this may cause an Exception. Perhaps this is why debugging it goes to another event, because an unhandled exception occurs and continues executing without giving any info (??)
Try adding this code to your database connection string and let me know if it works properly now.
MultipleActiveResultSets=true
Another option is get first reader into a dataset, close the datareader and iterate the dataset in the second bucle.
Hope it helps!
I'm trying to remove a row from the database that has the same ART as is selected in the combobox. I had it working before but when I changed the database it was supposed to delete it from it stopped working and gave me a error message. I did change the database connection etc acording to the database change.
The error message (Hoping image works)
I don't know why it says "conversion failed when converting the varchar value 'R06018' to data type int" since I don't have a value of R06018 anywhere in the code, nor is it the selected row.
the code I tried after the delete stopped working, it's just the delete without any thing extra (I know it doesn't dispose but the program crashes when it tries to read, and it's just for finding the issue)
try
{
SqlCommand inkoopartdelete = new SqlCommand("delete from ART where ART=" + artnr.SelectedItem + "", Connectie.connMEVO);
drMEVO = inkoopartdelete.ExecuteReader();
MessageBox.Show(this.artnr.SelectedItem + " verwijderd.");
}
catch (Exception e) { MessageBox.Show("" + e); }
The old code after I changed the db connection (set as comment since I tried a smaller bit of code for the delete)
//SqlCommand inkoopdelete = new SqlCommand("delete from ART where ART=#art", Connectie.connMEVO_ART);
//inkoopdelete.Parameters.Add("#art", SqlDbType.VarChar).Value = artnr.SelectedItem;
//drMEVO = inkoopdelete.ExecuteReader();
//try
//{
// while (drMEVO.Read())
// { }
// MessageBox.Show(this.artnr.SelectedItem + " verwijderd.");
//}
//catch (SqlException v)
//{
// MessageBox.Show("" + v);
//}
//inkoopdelete.Dispose();
I hope any of you could help me, since I can't find the issue.
Found the issue, see accepted awnser for error in test code, real error seems to be me reading over a part of the code -_- ...srry
If the ART field is of type nvarchar then, if you really want to use string concatenations, you should enclose your string value in single quotes and write
SqlCommand inkoopartdelete = new SqlCommand(#"delete from ART
where ART='" + artnr.SelectedItem + "'", Connectie.connMEVO);
That's a valid enough reason to revert as soon as possible to use a parameterized query as you have initially. Other reasons to avoid this is the fact that if your value has an embedded single quote the Whole text becomes syntactically wrong. And, finally, string concatenation is the open door for Sql Injection Attacks
A last note. If you want to execute a query like DELETE/INSERT or UPDATE do not use ExecuteReader. It works, but it is not necessary to build an SqlDataReader for that kind of queries. Just use
int affectedRows = inkoopartdelete.ExecuteNonQuery();
Change below statement :
You have to give single quote.
SqlCommand inkoopartdelete = new SqlCommand("delete from ART where ART='" + artnr.SelectedItem + "'", Connectie.connMEVO);
I am trying to update a mysql table while inside a c# for loop and a if statement well a few if statements. While running with a break point it will run the executenonquery once but the next loop it does not hit that. Even when i does hit the nonquery it does not change the table information.
the ffi string is the name of the column in my table and string val is what i want to put in. I know this is not the safe way to do it but I will change it when i can get it working the way it should.
Updated code it now runs the NONQUERY every time it should but still not updating the table
Code:
for (a = 0; a <= z; a++)
{
if (ds3.Tables[0].Rows[a][1].ToString() == dataGridView1.Rows[i].Cells[0].Value.ToString())
{
if (ds3.Tables[0].Rows[a][2].ToString() == dataGridView1.Rows[i].Cells[1].Value.ToString())
{
if (ds3.Tables[0].Rows[a][3].ToString() == dataGridView1.Rows[i].Cells[2].Value.ToString())
{
MessageBox.Show("We have a match " + dataGridView1.Rows[i].Cells[0].Value.ToString() + " " + dataGridView1.Rows[i].Cells[1].Value.ToString() + " " + dataGridView1.Rows[i].Cells[t].Value.ToString());
try
{
string ffi = textBox1.Text;
decimal val = decimal.Parse(dataGridView1.Rows[i].Cells[t].Value.ToString());
MySqlCommand cmd = new MySqlCommand("Update spt_results SET " + ffi + " = " + val + " where project_Id =" + dataGridView1.Rows[i].Cells[0].Value.ToString() + "",connection2);
//cmd.Connection = connection2;'
// cmd.Connection.Open();
cmd.ExecuteNonQuery();
//cmd.Connection.Close();
}
catch
{
}
The message box does show every loop and the connection2.open will run everytime
Thank you for looking and your help
The update string looks like "update spt_results SET FFI 300 = '15' where project_Id =AAA007" when it runs
Brent
Look at your code:
MySqlCommand cmd = new MySqlCommand();
cmd.CommandText = // ... snip SQL injection invitation
connection2.Open();
cmd.ExecuteNonQuery();
connection2.Close();
The MySqlCommand has no connection. You're opening and closing a connection, but it's got nothing to do with the command. I'd actually expect cmd.ExecuteNonQuery() to throw an exception because it has no connection...
Note that you should use using statements for the command and connection, to ensure that all the resources get cleaned up even in the face of an exception.
use cmd.Connection = connection2; just after connection2.Open();.
When you trying to execute the cmd.ExecuteNonQuery(), it is raising the error for no Connection bounded with the Command and error is caught in catch block. You didn't came to know because you have not doing anything in catch block for the errors.
If uncomment your code: The connection is open correctly and your code should work. But I'd suggest you to open connection once, before the loop, and close it at the end.
Another point is that you catched ALL exceptions, it is not good. The problem can be with the query, try to run "update spt_results SET FFI 300 = '15' where project_Id =AAA007" in the console or another MySQL client. It will throw an error. The field name 'FFI 300' must be quoted because it contains a white space and the value 'AAA007' must be quoted as a string literal. Try this query -
UPDATE spt_results SET `FFI 300` = '15' WHERE project_Id = 'AAA007'
I am dealing with the following problem:
I use a MSSQL Stored Procedure for displaying my data in a DataGridView. The Update and Insert Commands work, but there is one problem:
On inserting a new Row, the auto-numbered primary key isn't send back to my DataAdaptar. So the insertion is successfull in the database, but the PK is left blank in the DataGridView.
I allready tried some codes like:
private void _rowUpdated(object sender, SqlRowUpdatedEventArgs e)
{
if (e.Status == UpdateStatus.Continue && e.StatementType == StatementType.Insert)
{
cmd = conn.CreateCommand();
cmd.CommandText = "SELECT ##IDENTITY FROM " + e.Row.Table.TableName;
DataRow r = dt.Rows[dt.Rows.Count - 1];
r.ItemArray[0] = cmd.ExecuteScalar();
//r.SetModified(); --> err?
r.AcceptChanges();
}
}
on the DataAdapter, but nothing seems to work. All the SQL commands work fine.
When I refresh the data in the DataGridView, everyting is perfect. But the problem with this is, that the sort order and column width are adjusted. And that isn't what I want.
Can someone help me with this problem?
Looking forward for the solutions!
Thanks!
Finally found the answer and wanted to share it:
dt.RowChanged += new DataRowChangeEventHandler(_update_fields);
private void _update_fields(object sender, DataRowChangeEventArgs e)
{
try
{
if (e.Action == DataRowAction.Add)
{
conn.Open();
cmd = conn.CreateCommand();
cmd.CommandText = "SELECT IDENT_CURRENT('" + e.Row.Table.TableName + "')";
dt.Rows[dt.Rows.Count - 1][0] = int.Parse(cmd.ExecuteScalar().ToString()) + 1;
dt.AcceptChanges();
conn.Close();
}
adapt.Update(dt);
}
catch (SqlException ex)
{
Debug.WriteLine(ex.Message);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
Hope it will save you some time! :)
Gr
VeeWee
Since the connection to sql server has terminated so ##identity will become null and hence you are getting null value.
You cannot use scope_identity() here since its scope is limited to the place i.e. procedure in your case where it is called.
ident_current() always returns last identity value for the table that you specified.
It might not work correct in case of replication
Update your commandText to ="Select ident_current("+e.Row.Table.TableName+")"
Here are the results of using various techniques of retrieving identity value.
Replace dbo.M_PatientEntry with your table name
select ident_current('dbo.M_PatientEntry')
---------------------------------------
13
select ##identity from dbo.M_PatientEntry
---------------------------------------
NULL
NULL
select scope_identity()
---------------------------------------
NULL
select scope_identity() from dbo.M_PatientEntry
---------------------------------------
NULL
NULL
Also try avoiding ##Identity rather use scope_identity() or ident_current
##identity will give you incremented value of trigger table result if you are using trigger on the same table where insertion is going on.
see this documentation