I have a Window Application in ado.net with adding, modifying and deleting rows for a few tables. My problem is that after modifying a table with money type, the money value is much bigger after the operation.
String sql = "UPDATE kierowca SET imie='" + txtImie.Text + "',nazwisko='" + txtNazwisko.Text + "',data_zatrudnienia='" + txtData.Text + "',pensja='" + Convert.ToDecimal(txtPensja.Text) + "' WHERE imie='" + listBox2.SelectedItem.ToString() + "' AND nazwisko='" + listBox3.SelectedItem.ToString() + "';";
conn.Open();
cmd = new SqlCommand(sql, conn);
cmd.ExecuteNonQuery();
cmd.Clone();
conn.Close();
pensja in my table is type of money. What am I dong wrong?
I would give you an example of a parameterized query using your data
String sql = #"UPDATE kierowca SET imie=#imie,nazwisko=#nazwisko,
data_zatrudnienia=#data_zatrudnienia,pensja=#pensja
WHERE imie=#search1 AND nazwisko=#search2";
using(SqlConnection con = new SqlConnection(......))
using(SqlCommand cmd = new SqlCommand(sql, con))
{
cmd.Parameters.Add("#imie", SqlDbType.NVarChar).Value = txtImie.Text;
cmd.Parameters.Add("#nazwisko", SqlDbType.NVarChar).Value = txtNazwisko.Text;
cmd.Parameters.Add("#data_zatrudnienia", SqlDbType.NVarChar).Value = txtData.Text;
cmd.Parameters.Add("#pensja", SqlDbType.Decimal).Value = Convert.ToDecimal(txtPensja.Text);
cmd.Parameters.Add("#search1", SqlDbType.Decimal).Value = listBox2.SelectedItem.ToString();
cmd.Parameters.Add("#search2", SqlDbType.Decimal).Value = listBox3.SelectedItem.ToString();
con.Open();
int rowsChanged = cmd.ExecuteNonQuery();
MessageBox.Show("Updated " + rowsChanged + " rows");
}
Notice that I assume two things here.
The Convert.ToDecimal doesn't fails (better use decimal.TryParse to test if the input is indeed a decimal value).
The other fields involved in your query are all of type text (nvarchar on db)
Why this should work? Because with this code a parameter of type decimal and whose value is a decimal value is passed to the database engine. So the engine don't need to convert a string back to a decimal. This conversion could easily fails or give incorrect results if the a locale decimal point (comma) is not interpreted correctly by the database conversion code
Of course if your fields are of different type you should change the SqlDbType value in all the affected parameters
Related
I have a cell value in a column that is "12,000". And I want to change to "11,000" and display it... but it only displays a blank space. And in the database the value appears NULL.
In the database the type is Decimal(18,3).
My code in C# is like this:
decimal dec = Convert.ToDecimal(dgvRow.Cells[16].Value.ToString());
string query = "UPDATE cabecdoc SET CDU_Peso = TRY_CONVERT(DECIMAL(18,3),'" + dec + "' ) WHERE Id = '" + idDoc + "'";
If I do the query:
UPDATE CabecDoc
SET CDU_Peso = TRY_CONVERT(DECIMAL(18,3), '11.000')
WHERE Id = 'fb9668a9-46fa-11ec-9494-00155d01b010'
in Microsoft SQL Server - it works... but in my program in C# it displays a blank space value.
Ok, assuming the text value will NOT have the $ (or currency character), then this will work:
string strSQL = "UPDATE cabecdoc " +
"SET CDU_Peso = #Peso " +
"WHERE Id = #ID";
using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
{
conn.Open();
cmdSQL.Parameters.Add("#Peso", SqlDbType.Decimal).Value = dgvRow.Cells[16].Text;
cmdSQL.Parameters.Add("#ID", SqlDbType.NVarChar).Value = idDoc;
cmdSQL.ExecuteNonQuery();
}
So if the cell has:
12000
12,000
12,000.00
Then the above will work fine.
However, if the cell is to have:
$12,000.00
Then you need to use the globalization converters for this.
Say like this:
// -- using System.Globalization;
Decimal TestNum = 0;
decimal.TryParse(TextBox1.Text,
NumberStyles.Currency,
CultureInfo.CurrentCulture.NumberFormat, out TestNum);
Now, if the converter fails, then the TestNum will not be changed (the "out" return value in above), and then we now have this:
string strSQL = "UPDATE cabecdoc " +
"SET CDU_Peso = #Peso " +
"WHERE Id = #ID";
using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
{
conn.Open();
cmdSQL.Parameters.Add("#Peso", SqlDbType.Decimal).Value = TestNum;
cmdSQL.Parameters.Add("#ID", SqlDbType.NVarChar).Value = idDoc;
cmdSQL.ExecuteNonQuery();
}
And while the first will work as long as no currency character such as "$"?
Well, it will work, but first example does not handle "" (empty string).
so, you could say use this:
string MyPeso = dgvRow.Cell[16].Text;
if (MyPeso == "")
MyPeso = "0";
....
cmdSQL.Parameters.Add("#Peso", SqlDbType.Decimal).Value = MyPeso;
Also, as noted, not only is using paramters a lot easier - you don't even have to know (or think) if you need to surround the values - so the sql is much easier to read, and as noted, it is sql injection safe.
eg this:
Here are some simple codes that search for the value in TargetColumn where SourceColumn = SourceValue.
Here are these codes:
string cmdText = "select * from " + TableName + " where " + SourceColumn + " = '" + SourceValue + "'";
SqlCommand dbCommand = new SqlCommand(cmdText, dbConnection);
SqlParameter sqlParam = new SqlParameter("#" + SourceColumn, SourceValue);
dbCommand.Parameters.Add(sqlParam);
SqlDataReader dbReader = dbCommand.ExecuteReader();
dbReader.Read();
string _targetValue = dbReader[TargetColumn].ToString();
dbReader.Close();
dbCommand.Dispose();
return _targetValue;
And my questions are:
I passed SourceColumn and SourceValue to SqlCommand using SqlParameter, will this make it SQL injection proof?
Do I need to use TargetColumn together with SqlParameter too for SQL safety purpose? (but it is for SqlDataReader)
If I use SqlParameter for SqlCommand, do I still need to compose a command text in a string and pass it to SqlCommand before SqlParameter is used?
Why do I need to add an "#" for SourceColumn? (I just followed the tutorial and added it) And why SourceValue doesn't need an "#"?
The above codes works well to return the expected value, but I'm so not sure about the above questions.
Thanks very much!
i have a button that when clicked inserts data from textbox and combobox fields into database tables, but every time i insert it gives me "Invalid attempt to call read when reader is closed". How can i get rid of this error. And tips on optimising the code are welcome, because i know im a total noob. thanks
private void btnSave_Click(object sender, RoutedEventArgs e)
{
try
{
SqlConnection sqlCon = new SqlConnection(#"Data Source=(localdb)\mssqllocaldb; Initial Catalog=Storagedb;");
sqlCon.Open();
string Query1 = "insert into location(Storage, Shelf, columns, rows) values(" + txtWarehouse.Text + ", " + txtShelf.Text + ", " + txtColumn.Text + ", " + txtRow.Text + ")";
SqlCommand sqlCmd = new SqlCommand(Query1, sqlCon);
SqlDataAdapter dataAdp = new SqlDataAdapter(sqlCmd);
dataAdp.SelectCommand.ExecuteNonQuery();
sqlCon.Close();
}
catch (Exception er)
{
MessageBox.Show(er.Message);
}
try
{
SqlConnection sqlCon = new SqlConnection(#"Data Source=(localdb)\mssqllocaldb; Initial Catalog=Storagedb;");
sqlCon.Open();
string Query3 = "SELECT LOCATION_ID FROM LOCATION WHERE storage='" + txtWarehouse.Text + "' AND shelf='" + txtShelf.Text + "' AND columns='"
+ txtColumn.Text + "' AND rows='" + txtRow.Text + "'";
SqlCommand sqlCmd1 = new SqlCommand(Query3, sqlCon);
SqlDataReader dr = sqlCmd1.ExecuteReader(); ;
while (dr.Read())
{
string LocationId = dr[0].ToString();
dr.Close();
string Query2 = "insert into product(SKU, nimetus, minimum, maximum, quantity,location_ID,category_ID,OrderMail_ID) values ('" + txtSku.Text + "','" + txtNimetus.Text + "', '"
+ txtMin.Text + "', '" + txtMax.Text + "', '" + txtQuan.Text + "', '" + LocationId + "', '" + (cbCat.SelectedIndex+1) + "', '" + (cbMail.SelectedIndex+1) + "')";
SqlCommand sqlCmd = new SqlCommand(Query2, sqlCon);
SqlDataAdapter dataAdp = new SqlDataAdapter(sqlCmd);
dataAdp.SelectCommand.ExecuteNonQuery();
}
sqlCon.Close();
}
catch (Exception ed)
{
MessageBox.Show(ed.Message);
}
}
Let's try to make some adjustments to your code.
First thing to consider is to use a parameterized query and not a
string concatenation when you build an sql command. This is mandatory
to avoid parsing errors and Sql Injections
Second, you should encapsulate the disposable objects in a using statement
to be sure they receive the proper disposal when you have finished to
use them.
Third, you can get the LOCATION_ID from your table without running a
separate query simply adding SELECT SCOPE_IDENTITY() as second batch to your first command. (This works only if you have declared the LOCATION_ID field in the first table as an IDENTITY column)
Fourth, you put everything in a transaction to avoid problems in case
some of the code fails unexpectedly
So:
SqlTransaction tr = null;
try
{
string cmdText = #"insert into location(Storage, Shelf, columns, rows)
values(#storage,#shelf,#columns,#rows);
select scope_identity()";
using(SqlConnection sqlCon = new SqlConnection(.....))
using(SqlCommand cmd = new SqlCommand(cmdText, sqlCon))
{
sqlCon.Open();
using( tr = sqlCon.BeginTransaction())
{
// Prepare all the parameters required by the command
cmd.Parameters.Add("#storage", SqlDbType.Int).Value = Convert.ToInt32(txtWarehouse.Text);
cmd.Parameters.Add("#shelf", SqlDbType.Int).Value = Convert.ToInt32(txtShelf.Text);
cmd.Parameters.Add("#columns", SqlDbType.Int).Value = Convert.ToInt32(txtColumn.Text );
cmd.Parameters.Add("#rows", SqlDbType.Int).Value = Convert.ToInt32(txtRow.Text);
// Execute the command and get back the result of SCOPE_IDENTITY
int newLocation = Convert.ToInt32(cmd.ExecuteScalar());
// Set the second command text
cmdText = #"insert into product(SKU, nimetus, minimum, maximum, quantity,location_ID,category_ID,OrderMail_ID)
values (#sku, #nimetus,#min,#max,#qty,#locid,#catid,#ordid)";
// Build a new command with the second text
using(SqlCommand cmd1 = new SqlCommand(cmdText, sqlCon))
{
// Inform the new command we are inside a transaction
cmd1.Transaction = tr;
// Add all the required parameters for the second command
cmd1.Parameters.Add("#sku", SqlDbType.NVarChar).Value = txtSku.Text;
cmd1.Parameters.Add("#nimetus",SqlDbType.NVarChar).Value = txtNimetus.Text;
cmd1.Parameters.Add("#locid", SqlDbType.Int).Value = newLocation;
.... and so on for the other parameters required
cmd1.ExecuteNonQuery();
// If we reach this point the everything is allright and
// we can commit the two inserts together
tr.Commit();
}
}
}
}
catch (Exception er)
{
// In case of exceptions do not insert anything...
if(tr != null)
tr.Rollback();
MessageBox.Show(er.Message);
}
Notice that in the first command I use parameters of type SqlDbType.Int because you haven't used single quotes around your text. This should be verified against the real data type of your table columns and adjusted to match the type. This is true as well for the second command where you put everything as text albeit some of those fields seems to be integer (_location_id_ is probably an integer). Please verify against your table.
I keep getting the error in GetTotalMaterialCost I have already checked the whole thing. I even remove excess con.Close but the error will still prompt t it.
decimal GetTotalMaterialCost()
{
decimal total = 0;
con.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = con;
cmd.CommandText =
"SELECT SUM(rm.Quantity * m.SellingPrice) AS TotalMaterialCost FROM Resource_Materials rm " +
"JOIN Materials m ON m.MaterialID = rm.MaterialID " +
"JOIN ProjectTasks t ON t.TaskID = rm.TaskID " +
"WHERE t.TaskID=#TaskID HAVING COUNT (*) > 0";
cmd.Parameters.AddWithValue("#TaskID", Request.QueryString["ID"].ToString());
object data = cmd.ExecuteScalar();
if (data == null)
total = 0;
else
total = (decimal)cmd.ExecuteScalar();
con.Close();
return total;
}
protected void btnSave_Click(object sender, EventArgs e)
{
con.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = con;
cmd.CommandText = "UPDATE ProjectTasks SET Name=#Name, Description=#Description " +
"WHERE TaskID=#TaskID; " +
"SELECT TOP 1 TaskID FROM ProjectTasks ORDER BY TaskID DESC;";
cmd.Parameters.AddWithValue("#Name", txtName.Text);
cmd.Parameters.AddWithValue("#Description", txtDescription.Text);
cmd.Parameters.AddWithValue("#TaskID", Request.QueryString["ID"].ToString());
cmd.ExecuteNonQuery();
cmd.CommandText = #"UPDATE Resource_Materials SET TaskID=#TaskID WHERE TaskID=0; " +
"UPDATE Resource_Equipments SET TaskID=#TaskID WHERE TaskID=0; " +
"UPDATE Resource_Vehicles SET TaskID=#TaskID WHERE TaskID=0; " +
"UPDATE Resource_Contractors SET TaskID=#TaskID WHERE TaskID=0; " +
"UPDATE Projects SET ActualCost=#ActualCost WHERE ProjectID=#ProjectID";
cmd.Parameters.AddWithValue("#ProjectID", Request.QueryString["ID"].ToString());
cmd.Parameters.AddWithValue("#ActualCost", GetAmount());
con.Close();
Helper.AddLog("1", "Add", "Assigned Resources to Task");
Response.Redirect("~/Projects/Default.aspx");
}
Here is the link for my whole code as reference
The said error is at line 679
This usually happens because you use a global connection object and somewhere in your code the connection was not properly closed. For example, an exception is triggered somewhere in your code and you forget to close the connection in that case.
The resolution is always the same.
Do not keep a global connection object.
Create it when you need it and destroy it with the appropriate syntax.
There is connection pooling infrastructure in ADO.NET that has been devised just to give better performance when you are in a create/open/use/close scenario.
decimal GetTotalMaterialCost()
{
decimal total = 0;
string query = #"SELECT SUM(rm.Quantity * m.SellingPrice) AS TotalMaterialCost
FROM Resource_Materials rm
JOIN Materials m ON m.MaterialID = rm.MaterialID
JOIN ProjectTasks t ON t.TaskID = rm.TaskID
WHERE t.TaskID=#TaskID HAVING COUNT (*) > 0";
using(SqlConnection con = new SqlConnection(....constringhere...)
using(SqlCommand cmd = new SqlCommand(query, con))
{
con.Open();
.....
}
return total;
}
In this way the connection object is local and the using statement ensures that is closed and disposed at the closing brace even if you hit some kind of exception.
Of course this pattern should be applied in every point where you try to reach your database and the global connection object should be removed. The only thing that could be kept global is the connection string and also for this there is a better place to store it (IE. The app.config ConnectionString section)
Said that it is a possibility that you have an error caused by the AddWithValue usage. This method defines the DataType of the parameter looking at the value passed. It seems that your TaskID field is an integer, but you prepare a parameter with AddWithValue and pass a string. So the query will use a parameter with the wrong datatype.
I suggest to use
cmd.Parameters.Add("#TaskID", SqlDbType.Int).Value =
Convert.ToInt32(Request.QueryString["ID"].ToString()));
Finally, just give a cursory glance to your code, I suggest to change the methods called by your Page_Load to receive a connection object opened directly in the Page_Load event
if (!IsPostBack)
{
using(SqlConnection con = new SqlConnection(....constringhere...)
{
GetProjectMaterials(con);
GetProjectEquipments(con);
GetProjectVehicle(con);
GetProjectContractors(con);
GetTasks(con,resourceID);
GetMaterials(con);
GetEquipments(con);
GetVehicles(con);
GetLContractors(con);
}
}
Of course you need to change also the other methods that calls these methods to pass a connection, but if I am not wrong, you already have to build a connection in those callers.
I would suggest you to use local variable for connection together with "using" since .NET SQL provider will do connection pooling for you and "using" will ensure that your connection is properly closed and disposed. So your code will look like:
decimal GetTotalMaterialCost()
{
decimal total = 0;
using (var con = new SqlConnection(/*connection string if not configured via web.config*/))
{
con.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = con;
cmd.CommandText =
"SELECT SUM(rm.Quantity * m.SellingPrice) AS TotalMaterialCost FROM Resource_Materials rm " +
"JOIN Materials m ON m.MaterialID = rm.MaterialID " +
"JOIN ProjectTasks t ON t.TaskID = rm.TaskID " +
"WHERE t.TaskID=#TaskID HAVING COUNT (*) > 0";
cmd.Parameters.AddWithValue("#TaskID", Request.QueryString["ID"].ToString());
object data = cmd.ExecuteScalar();
if (data == null)
total = 0;
else
total = (decimal)cmd.ExecuteScalar();
}
}
return total;
}
You are calling cmd.ExecuteScalar() twice without resetting it. Your line setting the total variable should be total = (decimal) data;. After all, you already have the answer, why not just use it instead of re-executing the code?
Hello I want to store datetimepicker value into mysql database my code is given below
dtpDate = datetimepicker1.value.date;
dtpTime = datetimepicker2.value.Timeofday;
MySqlCommand cmd = new MySqlCommand("INSERT INTO schedule_days(schedule_name,start_time,status,days,start_date,connector_id) VALUES ('" + name + "','" + dtpTime + "','" + s + "','" + day + "','"+dtpDate+"','" + chkArray[i].Tag + "')", con);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
but no value is being stored at database
and at that place there is unable to read data comes.
what may be the problem?
The Value is not being entered at MySQL database because there is mistake in your query at dtpTime and dtpDate fields.
you shout replace it whith dtpTime.Value.TimeofDay and dtpDate.Value.Date ane new query will be like this
dtpDate = datetimepicker1.value.date;
dtpTime = datetimepicker2.value.Timeofday;
MySqlCommand cmd = new MySqlCommand("INSERT INTO schedule_days(schedule_name,start_time,status,days,start_date,connector_id) VALUES ('" + name + "','" + dtpTime.Value.TimeofDay + "','" + s + "','" + day + "','"+dtpDate.Value.Date.ToString("yyyy-MM-dd HH:mm")+"','" + chkArray[i].Tag + "')", con);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
Well, it may not be the cause of the problem (are there any exceptions? What does ExecuteNonQuery return?) but you should definitely not be building up your SQL like this. It leads to SQL injection attacks, as well as data conversion problems.
Instead, you should use parameterized SQL:
using (MySqlConnection conn = new MySqlConnection(...))
{
conn.Open();
using (MySqlCommand cmd = new MySqlCommand(
"INSERT INTO schedule_days(schedule_name,start_time,status,days,start_date,connector_id) " +
"VALUES (#name, #time, #status, #days, #date, #connector)", conn))
{
cmd.Parameters.Add("#name", MySqlDbType.VarChar).Value = name;
cmd.Parameters.Add("#time", MySqlDbType.Time).Value = dtpTime;
cmd.Parameters.Add("#status", MySqlDbType.VarChar).Value = s;
cmd.Parameters.Add("#days", MySqlDbType.Int32).Value = day;
cmd.Parameters.Add("#date", MySqlDbType.Date).Value = dtpDate;
cmd.Parameters.Add("#connector", MySqlDbType.VarChar).Value = chkArray[i].Tag;
int insertedRows = cmd.ExecuteNonQuery();
// TODO: Validate that insertedRows is 1?
}
}
I've guessed at the data types - please check them against your actual database.
Using NuGet Package MySql.Data 6.6.4.
Currently MySqlParameter does not support unnamed parameters. Parameters must begin with with a ?.
Example:
command.Parameters.AddWithValue("?Parameter", value);
Something like this should work. Avoid string concatenation with Sql because that can lead to security risks.
dtpDate = datetimepicker1.value.date.ToString("yyyy-MM-dd HH:mm"); //Formatted Date for MySql
dtpTime = datetimepicker2.value.Timeofday;
using(var connection = new MySqlConnection(connectionString))
{
using(var command = connection.CreateCommand())
{
command.CommandText = "INSERT INTO schedule_days(schedule_name,start_time,status,days,start_date,connector_id) VALUES ( ?ScheduleName, ?StartTime, ?Status, ?Days, ?StartDate, ?ConnectorId )";
command.Parameters.AddWithValue("?ScheduleName", name);
command.Parameters.AddWithValue("?StartTime", dtpTime);
command.Parameters.AddWithValue("?Status", s);
command.Parameters.AddWithValue("?Days", day);
command.Parameters.AddWithValue("?StartDate", dtpDate);
command.Parameters.AddWithValue("?ConnectorId", chkArray[i].Tag);
connection.Open();
command.ExecuteNonQuery();
}
}