C# - How To Loop Through A Table To Update Each Row MySQL - c#

I'm working on a project where I have to update a table in MySql by looping through it and update every row (one by one) in C#. NOTE: I have to update the value's of only one column in my table.
Now, I know this question has been asked (many times) before but after spending a lot of time searching around the internet I haven't found a solution for my problem.
More Explanation:
So I already know a few things that are necessary to make this work:
I know how to establish a (working) SSH connection.
I know how to establish a (working) MySql connection to my database.
I know how to run/execute a SELECT and UPDATE Query/Command in C#.
So the only thing that I need to make this whole thing work, is the loop itself.
Now, during my research I came across some answers that suggested using a Reader. I don't know if this is what I need and (if yes) how to use it correctly.
Below is the code that I have so far (for establishing the connections and executing query's).
My Code:
class ReaderDemo1
{
public static void Update()
{
Console.WriteLine("[Core] Opening Connection To Database...");
Database.openStockConn(Settings.databaseName, Settings.databaseUsername, Settings.databasePassword, Settings.sshHost, Settings.sshUsername, Settings.sshPassword);
if (Database.stockConn.State != ConnectionState.Open)
{
Database.openStockConn(Settings.databaseName, Settings.databaseUsername, Settings.databasePassword, Settings.sshHost, Settings.sshUsername, Settings.sshPassword);
}
Console.WriteLine("[Core] database connection is now open!\n");
MySqlCommand cmd = new MySqlCommand("SELECT value FROM catalog_product_entity_decimal", Database.stockConn);
try
{
Console.WriteLine("[Price] Updating Prices...");
MySqlCommand command = new MySqlCommand("UPDATE catalog_product_entity_decimal SET value= 1112 WHERE value_id= 4063", Database.stockConn);
command.ExecuteNonQuery();
Console.WriteLine("[Price] Prices Have Been Updated!");
}
catch
{
Console.WriteLine("Updating Failed!");
}
finally
{
if (Database.stockConn != null)
{
Database.stockConn.Close();
}
}
}
}
Just to give some extra context:
Database.cs is where I create the connections (SSH and MySql)
Settings.cs is where I have all the Login data for the SSH and MySql connections.
If you guys would want to know, I have already attempted a few things myself regarding the loop but (as I already mentioned) these attempts weren't successful.
My Attempt With Reader:
using (var reader = command.ExecuteReader())
{
var indexOfValue = reader.GetOrdinal("value");
while (reader.Read())
{
var price1 = reader.GetValue(indexOfValue);
Console.WriteLine("Executing Update Command...");
MySqlCommand cmd = new MySqlCommand("UPDATE catalog_product_entity_decimal SET value= 1222 WHERE entity_id= 759 AND entity_id= 839 AND entity_id= 881", con);
cmd.ExecuteNonQuery();
Console.WriteLine("Update Command Executed!");
}
}
As said, the code above didn't work how I wanted it to work (probably because it isn't actually doing anything now). Just to let you guys know, I am not stuck at an error. I'm just stuck on how to do this.
I hope one of you guys can help me with this or point me in the rigth direction and if you think that my question is a duplicate of another one, just tell me and I will look into it! :) Thanks in advance.
Kind regards,
LKS
EDIT:
In case you guys wanted to know, this is what my table looks like.
The table contains about 5600 rows, so these are just the top rows.

Like Frederiks answer, the thing that you are trying to do now is Looping over an table and execute the same Query over and over again.
Even if you manage to get your code working it would be very slow (because you have about 5600 rows to update).
So my suggestion is that you create an new table with the new value's in it (so the one's you wanted to have after your loop). Then just run a single update command to update your old table with values from your new table.
This option probably takes a few seconds and it will be done, so its much faster! :)
The Query you need should look something like this:
UPDATE old_table
INNER JOIN new_table
USING (column) --> // if you want to update a specific column
EDIT:
In addition/ update to my answer, this is how you can update your table more accurate:
UPDATE old_table
INNER JOIN new_table ON old_table.value_id = new_table.value_id // Use this to set your columns with unique values's
SET old_table.value = new_table.value // For the column with the value's you want to update
So, in the above code you update your old_table with the value's from your new_table. In this example you only update the value's from only one column (which you wanted).
You can expand the query for a different result.

I cannot think of any reason that you would want to read one line at a time and write one line at a time in a loop, but this is how you could do it.
Use one SqlCommand to read and a separate SqlCommand to update. Change the parameters in the loop for each update.
public static void Update()
{
Database.openStockConn(Settings.databaseName, Settings.databaseUsername, Settings.databasePassword, Settings.sshHost, Settings.sshUsername, Settings.sshPassword);
if (Database.stockConn.State != ConnectionState.Open)
{
Database.openStockConn(Settings.databaseName, Settings.databaseUsername, Settings.databasePassword, Settings.sshHost, Settings.sshUsername, Settings.sshPassword);
}
Console.WriteLine("[Core] database connection is now open!\n");
MySqlCommand cmd1 = new MySqlCommand("SELECT value FROM catalog_product_entity_decimal", Database.stockConn);
MySqlCommand cmd2 = new MySqlCommand("UPDATE catalog_product_entity_decimal SET value= 1112 WHERE value_id= 4063", Database.stockConn);
try
{
Console.WriteLine("[Price] Updating Prices...");
using (var reader = cmd1.ExecuteReader())
{
var indexOfValue = reader.GetOrdinal("value");
while (reader.Read())
{
var price1 = reader.GetValue(indexOfValue);
Console.WriteLine("Executing Update Command...");
cmd2.ExecuteNonQuery();
Console.WriteLine("Update Command Executed!");
}
}
}
catch
{
Console.WriteLine("Updating Failed!");
}
finally
{
if (Database.stockConn != null)
{
Database.stockConn.Close();
}
}
}

SQL is set based, which means that there is almost always a better approach then looping over a table.
That is also the case with your problem. You want to avoid looping over an entire table to update record-by-record, since this is terribly slow.
It is not obvious to me on what you're trying to achieve, since your code sample loops over a table and then inside your loop, you execute the same statement over and over again, but you're always updating the exact same records ?
If you want to update records in a table with information coming from another table, you might want to have a look at the UPDATE statement together with the JOIN clause as explained here.

You can try this change , by having the count of rows and then updating the records.
MySqlCommand cmd = new MySqlCommand("SELECT count(*) FROM catalog_product_entity_decimal", Database.stockConn);
try
{
var reader = cmd .ExecuteReader();
if(reader.Read()){
var totalRows=reader.GetInt32(0);
Console.WriteLine("[Price] Updating Prices...");
while(totalRows-->0){
MySqlCommand command = new MySqlCommand("UPDATE catalog_product_entity_decimal SET value= 1112 WHERE value_id= 4063", Database.stockConn);
command.ExecuteNonQuery();
}
Console.WriteLine("[Price] Prices Have Been Updated!");
}
}
catch
{
Console.WriteLine("Updating Failed!");
}
finally
{
if (Database.stockConn != null)
{
Database.stockConn.Close();
}
}

Related

Code runs but doesn't update database

This code is supposed to save some values in textboxes to a specific row. The code runs just fine with no hiccups, but refuses to actually update the database no matter what I do.
try
{
using (var con = new OleDbConnection())
{
con.ConnectionString = #"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\User\Desktop\esoft\gym\gym\bin\Debug\Clients.accdb;";
con.Open();
using (var com = new OleDbCommand())
{
com.Connection = con;
com.CommandText = "UPDATE gym SET BMI = #bmi and Health = #health and weight_change_to_healthy_bmi = #weight WHERE ID = #id";
com.Parameters.AddWithValue("#bmi", bmi.Text);
com.Parameters.AddWithValue("#health", health.Text);
com.Parameters.AddWithValue("#weight", change.Text);
com.Parameters.AddWithValue("#id", id.Text);
com.ExecuteNonQuery();
}
}
MessageBox.Show("Saved");
}
catch (Exception ex)
{
MessageBox.Show("Not saved: " + ex.Message);
}
Any help would be much appreciated.
As Alex mentioned, SET part needs , instead of AND for multiple columns.
Check UPDATE syntax1;
UPDATE table_name
SET column1=value1,column2=value2,...
WHERE some_column=some_value;
But I wanna say a few things more;
Don't use AddWithValue as much as you can. It may generate unexpected and surprising results sometimes. Use Add method overload to specify your parameter type and it's size.
Open your connection just before you execute your command. That means, you should open your connection just before your ExecuteNonQuery line.
Based on it's name, ID column should be some numeric value instead of character. Consider to change it's type or consider to change it's column name that refers some character typed column name.
1: I know I know.. a w3school link

c# mysql sum query to label

I'm trying to do something I feel is quite simple in the grand scheme of things, however I'm clearly missing something. What I have is a simple database named 'localcollection'. What I would like to do is sum up the dollar amount of a column named 'purprice', and setting it as the text of a label (label4). I've been finding variants of code throughout the last couple days that suggest different ways of achieving this. The majority of my digging suggest that using ExecuteScalar is what I want to do. The code that I've been fumbling with follows.
SqlCeConnection myconn = new SqlCeConnection(Properties.Settings.Default.localbotdbConnectionString);
myconn.Open();
{
string result = "select sum(purprice) from localcollection";
SqlCeCommand showresult = new SqlCeCommand(result, myconn);
label4.Text = showresult.ExecuteScalar().ToString();
myconn.Close();
}
Others suggest using the SqlCeReader. I'm impartial to either of them, as long as one of them works, and I am clearly missing something (fault of my own). The reader rendition that I was trying to make work is:
SqlCeCommand cmd = new SqlCeCommand("select sum(purprice) from localcollection");
SqlCeDataReader reader = cmd.ExecuteReader();
while(reader.Read())
{
label4.Text = reader.GetString(0);
}
myconn.Close();
Constructive advice appreciated. Thank you
If you are only looking to return one value from a query, then ExecuteScalar is what you should be using, . The ExecuteReader is better for forward-only reads of multiple records, so it's overkill for your situation
Take a look here for a comparisson What is the difference between ExecuteScalar, ExecuteReader and ExecuteNonQuery?
I would do some modifications to your code because for one thing your are not properly disposing of your objects, also you stated that you have it in the button click method which I would get that out of there and make this its own function.
private string performSQL()
{
string result = "select sum(purprice) from localcollection";
using (SqlCeConnection myconn = new SqlCeConnection("ConnectionString"))
using (SqlCeCommand showresult = new SqlCeCommand(result, myconn))
{
try
{
myconn.Open();
return showresult.ExecuteScalar().ToString();
}catch(System.Exception ex)
{
MessageBox.Show(ex.ToString());
// or log exception how ever you prefer
}finally
{
//the finally ensures your connection gets closed
myconn.Close();
}
}
return "";
}
Best practice, use ExecuteScalar when you are returning 1 row and 1 column of data (which your query does.) As a result, go with ExecuteScalar.
Make sure the name of the column you are trying to add is purprice, and that it is a numeric type.
Also make sure it doesn't contain NULL values.
I think your code is ok, but you missed :
SqlCeCommand cmd = new SqlCeCommand("select sum(purprice) from localcollection",myconn);
that's all, hope it works
Just add AS after SUM() like the row below:
select sum(purprice) AS purprice from localcollection
And you are good to go.

Best way to move data between tables in MYSQL

I have a question regarding performance. This is my scenario.
I have a MYSQL database and a application that from time to time moves records, according to the criteria from a query, from one table to another. The way this is done is:
foreach(object obj in list)
{
string id = obj.ToString().Split(',')[0].Trim();
string query = " insert into old_records select * from testes where id='" +
id + "';" + " delete from testes where id='" + id +"'";
DB _db = new DB();
_db.DBConnect(query);
this is the way I connect to the database:
DataTable _dt = new DataTable();
MySqlConnection _conn = new MySqlConnection(connectionString);
MySqlCommand _cmd = new MySqlCommand
{
Connection = _conn,
CommandText = query
};
MySqlDataAdapter _da = new MySqlDataAdapter(_cmd);
MySqlCommandBuilder _cb = new MySqlCommandBuilder(_da);
_dt.Clear();
try
{
_conn.Open();
_cmd.ExecuteNonQuery();
_da.Fill(_dt);
}
catch (MySqlException ex)
{
Console.WriteLine(ex.Message);
}
finally
{
if (_conn != null) _conn.Close();
}
return _dt;
So my question is, I have like 4000 rows in the table, and it takes a lot of time to move all the records from one table to another, specially across a network. Is there a way to make this faster?
I have been doing some reading and there are several option to handle data from DB like data adapters, reader, set, and tables. Which one is faster for this case? Should I be using a different method?
Two things I see is that first you're opening and closing your connection for each insert, that's usually your most expensive operation so you won't want to do that. You can also try batching them rather than doing them at once. When you do that you have to be careful because things could break in the middle of a large update so you would want to do things in a transaction. Without knowing too much about what your data structure looks like I refactored your method to do batching 100 at a time. First create a little helper method called move items that takes a connection and a list of ids. Don't do a try catch in this, you'll see why later.
Note: This method doesn't use parameters, I highly recommend you change it to do that.
private static void MoveItems(MySqlConnection conn, List<string> moveList)
{
string query = string.Format("insert into old_records select * from testes where id IN({0});" + " delete from testes where id IN({0})", string.Join(",", moveList.ToArray()));
var cmd = new MySqlCommand
{
Connection = conn,
CommandText = query
};
cmd.ExecuteNonQuery();
}
Next you'll change your main method to open the database connection once and then call this method 100 id's at a time. This method will have a try catch therefore if the call to MoveItems throws an exception it will be caught in this main method.
// the using statement will call your dispose method
using (var conn = new MySqlConnection(connectionString))
{
// open the connection and start the transaction
conn.Open();
var transaction = conn.BeginTransaction();
// createa list to temporarily store the ids
List<string> moves = new List<string>();
try
{
// clean the list, do the trim and get everything that's not null or empty
var cleanList = list.Select(obj => obj.ToString().Split(',')[0].Trim()).Where(s => !string.IsNullOrEmpty(s));
// loop over the clean list
foreach (string id in cleanList)
{
// add the id to the move list
moves.Add("'" + id + "'");
// batch 100 at a time
if (moves.Count % 100 == 0)
{
// when I reach 100 execute them and clear the list out
MoveItems(conn, moves);
moves.Clear();
}
}
// The list count might not be n (mod 100) therefore see if there's anything left
if (moves.Count > 0)
{
MoveItems(conn, moves);
moves.Clear();
}
// wohoo! commit the transaction
transaction.Commit();
}
catch (MySqlException ex)
{
// oops! something happened roll back everything
transaction.Rollback();
Console.WriteLine(ex.Message);
}
finally
{
conn.Close();
}
}
You may have to play with that 100 number. I remember when I worked with MySQL a lot I saw some performance differences between doing an IN and giving it a list of Or statements (Id = 'ID1' OR id = 'ID2' ...). But executing 40 statements or 80 statements will certainly have better performance, and opening the database connection once instead of 4000 times should also give you much better performance.
I might be wrong, but there is not much you can do in order to make it faster. After all you want to get the entire table data and insert its information into another table. The process will take some time if your table isn't small. However, you can try using the code below. It should do the trick and save some time.
INSERT INTO TABLE2 (FIELDNAME_IN_TABLE2, FIELDNAME2_IN_TABLE2)
SELECT FIELDNAME_IN_TABLE1, FIELDNAME2_IN_TABLE1
FROM TABLE1

List<T> to database and back again

So im trying to write my list to a database and ive got some questions. First of all nothing seems to get saved into the database but theres no errors that would cause a crash sofar and I know for a fact that this piece of code has worked in the past for me:
public void saveToDb(int var1, string var2)
{
SqlCommand cmd = new SqlCommand("Insert into [table] (col1, col2) VALUES (#param1, #param2)", conn);
cmd.Parameters.AddWithValue("#param1", var1);
cmd.Parameters.AddWithValue("#param2", var2);
cmd.Connection.Open();
cmd.ExecuteNonQuery();
cmd.Connection.Close();
}
I have a three piece layer going on, ui, logic and DAL. First of all it didnt work with the database and the app.config files in the DAL class library so those were moved to the main class library and now its not whining about the database already existing and it finds my connection string.
To send the data into this method im doing it this way:
for (int i = 0; i < myList.Count; i++)
{
da.saveToDb(myList.val1, myList.val2);
}
Its not giving me an error in the code but nothing seems to be saved or is resetted when I stop the program but I dont know which but I can see the variable being passed correctly by printing them before doing the insert so im guessing the Db is resetted? Is it putting my db into the debug folder and flushing it everytime or what?
Another thing is last time I did this it was a form so there was always going to be just one insert at a time, right now my list of objects could contain anywhere from 1 to alot of objects and with this way I would be opening and closing the db connection once for each object in my list right? How do you do bulk inserts? Been reading about datasets but they all seem to be about reading from a database not writing so im not sure about that. I did read from a database in another project using Dataset and adapter so that shouldnt be a problem but how do I bulk send a list into a table? To make it abit more tricky I cant just convert the entire list because of 10 propertys 8 is going to go into one table and the remaining 2 is to go in a second table so I would need to loop them and add the respective property to the respective dataset (or what you use).
edit
Well ive now made some adjustments and im trying two methods and none of which work but neither gives an error either which is getting frustrating.
First method:
public void saveToDb(int val1, string val2)
{
SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["[MyConnectionString]"].ConnectionString);
SqlCommand cmd = new SqlCommand("Insert into [table] (val1, val2) VALUES (#param1, #param2)", conn);
cmd.Parameters.AddWithValue("#param1", val1);
cmd.Parameters.AddWithValue("#param2", val2);
cmd.Connection.Open();
cmd.ExecuteNonQuery();
cmd.Connection.Close();
}
And the second I try to sqlbulkcopy a table:
public void SaveToDb()
{
using (SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["[MyConnectionString]"].ConnectionString))
{
conn.Open();
using (SqlBulkCopy bulk = new SqlBulkCopy(conn))
{
bulk.ColumnMappings.Add("col1", "col1");
bulk.ColumnMappings.Add("col2", "col1");
bulk.DestinationTableName = "[table]";
System.Diagnostics.Debug.Print("Open");
bulk.WriteToServer(tab);
foreach (DataRow row in tab.Rows)
{
foreach (var item in row.ItemArray)
{
System.Diagnostics.Debug.Print(item.ToString());
}
}
System.Diagnostics.Debug.Print("sending");
}
System.Diagnostics.Debug.Print("closing");
}
}
I shouldnt have to map it since the table contains the exact same columns as the table with the same naming (upper/lower case) but it gets weird with the primary key which increments so I mapped it so it should add the increment automatically but nothing gets inserted, the print sure enough shows the values but if I comment out the mapping I get an error saying that null cant be assigned (which is true my table wont take nulls) but the value shouldnt be null since its there in the table?
It seems you forgot the index.
for (int i = 0; i < myList.Count; i++)
{
da.saveToDb(myList[i].val1, myList[i].val2);
}
I think you are missing the connection object.
var cn = new SqlConnection().
Then you call open on the connection object.
connection.command will give access to the command object.

SQL Server CE WHERE statement behaving wrong? very confused

I am developing a windows mobile app. Right now I am just testing that it can correctly query the local SQL Server CE database. It works fine until I put a WHERE statement in.
Here is my code:
private void buttonStart_Click(object sender, EventArgs e)
{
System.Data.SqlServerCe.SqlCeConnection conn = new System.Data.SqlServerCe.SqlCeConnection(
("Data Source=" + (System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase), "ElectricReading.sdf") + ";Max Database Size=2047")));
try
{
// Connect to the local database
conn.Open();
System.Data.SqlServerCe.SqlCeCommand cmd = conn.CreateCommand();
SqlCeParameter param = new SqlCeParameter();
param.ParameterName = "#Barcode";
param.Value = "%" + textBarcode.Text.Trim() + "%";
// Insert a row
cmd.CommandText = "SELECT * FROM Main2 WHERE Reading LIKE #Barcode";
cmd.Parameters.Add(param);
cmd.ExecuteNonQuery();
DataTable data = new DataTable();
using (SqlCeDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
data.Load(reader);
}
}
if (data != null)
{
this.dataGrid1.DataSource = data;
}
}
finally
{
conn.Close();
}
The database contains this data:
Okay so you can see I changed the WHERE statement to use the Reading column just for testing purposes. When I enter "111" into the textbox and run --> it returns only the row where reading ="1111" and not the row that contains "111".
If I enter "1111" it does not return any data.
If I enter "1" it will return both the "1111" row and the "111" row which is the correct behavior.
However if I enter "11" it once again only returns the "1111" row.
Any other data entry of 2's or 9's attempting to return those rows does not work.
I'm not sure what is going on? This does not make any sense. It is not behaving like I would expect in any way shape or form. I know this must be a little confusing to read. I hope it makes enough sense to get some answers. Please help!
NOTE: I added the "%" before and after the text in an attempt to get better results. This is not desired.
EDIT <<<-----------------------I did have Reading = #Barcode, I just accidently typed Location for this question, that is not the problem.
Firstly, some things to note:
1) As other commentators have noted, use the Reading column, not the Location column. I know you have mentioned you are testing, but swapping around column names and then changing code isn't the easiest way to troubleshoot these things. Try to only change one thing at a time.
2) If Reading is numeric, you are going to have to convert the column value first.
So your query becomes:
"SELECT * FROM Main2 WHERE CONVERT(varchar, Reading) LIKE #Barcode";
Also see How to use parameter with LIKE in Sql Server Compact Edition for more help with working with parameters in SqlServerCE.
3) Set a parameter type on your SqlCEParameter. I've linked to the appropriate page in the code example below.
4) You are using ExecuteNonQuery for no reason. Just get rid of it in this context. It's for when you want to make a change to the database (like an insert, update, delete) or execute something (like a stored proc that can also insert, update, delete etc) that returns no rows. You've probably cut and paste this code from another place in your app :-)
5) Use using on disposable objects (see example below). This will make managing your connection lifecycle much simpler. It's also more readable (IMO) and will take care of issues when exceptions occur.
6) Use the using statement to import the BCL (Base Class Libraries) into your current namespace:
Add the following using statements to the top of your class (.cs). This will make using all of the .Net classes a lot simpler (and is much easier to read and less wear on your keyboard ;-)
using System.Data.SqlServerCe;
using System.IO;
using System.Reflection;
A more complete example would look like the following
private void buttonStart_Click(object sender, EventArgs e)
{
using(SqlCeConnection conn = new SqlCeConnection(
("Data Source=" + (Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase), "ElectricReading.sdf") + ";Max Database Size=2047"))))
{
// Connect to the local database
conn.Open();
using(SqlCeCommand cmd = conn.CreateCommand())
{
SqlCeParameter param = new SqlCeParameter();
param.ParameterName = "#Barcode";
param.DBType = DBType.String; //Intellisense is your friend here but See http://msdn.microsoft.com/en-US/library/system.data.sqlserverce.sqlceparameter.dbtype(v=VS.80).aspx for supported types
param.Value = "%" + textBarcode.Text.Trim() + "%";
// SELECT rows
cmd.CommandText = "SELECT * FROM Main2 WHERE CONVERT(varchar, Reading) LIKE #Barcode";
cmd.Parameters.Add(param);
//cmd.ExecuteNonQuery(); //You don't need this line
DataTable data = new DataTable();
using (SqlCeDataReader reader = cmd.ExecuteReader())
{
data.Load(reader); //SqlCeDataReader does not support the HasRows property.
if(data.Rows.Count > 0)
{
this.dataGrid1.DataSource = data;
}
}
}
}
}
Intellisense should be able to clean up any errors with the above but feel free to ask for more help.
Finally, you also might be able to set the data source of the grid directly to a datareader, try it!
using (SqlCeDataReader reader = cmd.ExecuteReader())
{
dataGrid1.DataSource = reader;
}
You can then get rid of the DataTable.
Change the following line:
cmd.CommandText = "SELECT * FROM Main2 WHERE Location LIKE #Barcode";
to
cmd.CommandText = "SELECT * FROM Main2 WHERE Reading LIKE #Barcode";
You are comparing the wrong columns.

Categories

Resources