I am using c# windows forms application connected to a Microsoft access database. I want the txtOrderMore text box to either show "yes" if txtSupplyLeft is less than 5 and "no" if txtSupplyLeft is greater than 5. I wanted this condition to happen once I inserted a new record into the database. This is what I came up with
private void btnSave_Click(object sender, EventArgs e)
{
try
{
connection.Open();
OleDbCommand command = new OleDbCommand();
command.Connection = connection;
command.CommandText = "insert into Products (ProductName, ProductPrice, Quantity, [Weight(g)], DaysSupplyLeft) values ('"+txtProductName.Text+"', "+txtProductPrice.Text+ ", " + txtQuantity.Text + ", " + txtWeight.Text + ", " + txtSupplyLeft.Text + ")";
command.ExecuteNonQuery();
txtProductName.Text = "";
txtProductPrice.Text = "";
txtQuantity.Text = "";
txtWeight.Text = "";
txtSupplyLeft.Text = "";
MessageBox.Show("Data saved");
connection.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error" + ex);
}
try
{
connection.Open();
OleDbCommand command = new OleDbCommand();
command.Connection = connection;
command.CommandText = "insert into Products ([OrderMore?]) values ('Yes') where "+txtSupplyLeft.Text+"<5";
command.ExecuteNonQuery();
txtOrderMore.Text = "Yes";
MessageBox.Show("Data saved");
connection.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error" + ex);
}
try
{
connection.Open();
OleDbCommand command = new OleDbCommand();
command.Connection = connection;
command.CommandText = "insert into Products ([OrderMore?]) values ('No') where "+txtSupplyLeft.Text+"";
command.ExecuteNonQuery();
txtOrderMore.Text = "No";
connection.Close();
}
catch(Exception ex)
{
MessageBox.Show("Error" + ex);
}
}
But whenever I run this code, the condition that outputs "yes" doesn't work, a.k.a I never get a yes when I input a value lower than 5. It does output no, but whenever I save a new record I get connection exceptions, it tells me the connection hasn't been closed when I clearly added "connection.Close()" at the end of each try loops.
I am very new to windows forms and SQL queries therefore I am not really sure where I could have gone wrong.
I have seen many comments before recommending parameterized queries but I'm not too sure how to do that.
When using parameters with OleDbCommand, it's important to know the following:
OleDbCommand
The OLE DB .NET Provider does not support named parameters for passing
parameters to an SQL statement or a stored procedure called by an
OleDbCommand when CommandType is set to Text. In this case, the
question mark (?) placeholder must be used. For example:
SELECT * FROM Customers WHERE CustomerID = ?
Therefore, the order in which OleDbParameter objects are added to the
OleDbParameterCollection must directly correspond to the position of
the question mark placeholder for the parameter in the command text.
Below, I'll show how to insert data to an Access database, as well as, how to update data in an access database. I'll be using OleDbParameter throughout the code.
Access database name: Database1.accdb
In Access (Design View),
Table Name: Product
ID: AutoNumber
ProductName: Short Text (General tab => Field Size: 50)
Price: Currency (General tab => Format: Currency)
Quantity: Number
Weight(g): Number
DaysSupplyLeft: Number
ReorderWhenQuantityReaches: Number
OrderMore: Yes/No (General tab => Format: Yes/No)
If creating the table programmatically, use the following column definitions:
Table Name: Product
ID: AutoIncrement not null Primary Key
ProductName: varchar(50) not null
Price: currency
Quantity: integer
Weight(g): integer
DaysSupplyLeft: integer
ReorderWhenQuantityReaches: integer
OrderMore: bit
Note: After the table is created programmatically, to make it look as desired in Access, it may be necessary to change the "Format" property for some of the Fields (Columns). Open the database in Access. Right-click "Product". Select "Design View". Then set the properties as described above in "In Access (Design View)".
In your VS project, ensure that your project has a reference for System.Data.
VS 2019:
In VS menu, click Project
Select Add Reference
Click Assemblies
Check System.Data
Create a class named HelperAccess
In VS menu, click Project
Select Add Class... (name: HelperAccess.cs)
Add the following using statement to HelperAccess.cs:
using System.Data.OleDb;
In "HelperAccess.cs" add the following variables:
private string _databaseFilename = string.Empty;
private string _connectionString = string.Empty;
Then add the following constructor:
public HelperAccess(string databaseFilename)
{
_databaseFilename = databaseFilename;
_connectionString = String.Format("Provider = Microsoft.ACE.OLEDB.12.0; Data Source = {0};Persist Security Info=False;", _databaseFilename);
}
In "HelperAccess.cs", create a method named AddProduct, add a variable to return, and add the return statement
public int AddProduct(string productName, decimal price, int quantity, double weightInGrams, int daysSupplyLeft, int reorderWhenQuantityReaches, bool orderMore = false)
{
int result = 0;
return result;
}
We'll start by creating the SQL, so we know what parameters need to specified.
string sqlText = "INSERT INTO Product (ProductName, Price, Quantity, [Weight(g)], DaysSupplyLeft, ReorderWhenQuantityReaches, OrderMore) VALUES(?, ?, ?, ?, ?, ?, ?)";
Next, we'll create an instance of OleDbConnection and open the connection. When a using statement is used, it ensures that the object is closed and/or disposed when execution goes out of scope.
public int AddProduct(string productName, decimal price, int quantity, double weightInGrams, int daysSupplyLeft, int reorderWhenQuantityReaches, bool orderMore = false)
{
int result = 0;
string sqlText = "INSERT INTO Product (ProductName, Price, Quantity, [Weight(g)], DaysSupplyLeft, ReorderWhenQuantityReaches, OrderMore) VALUES(?, ?, ?, ?, ?, ?, ?)";
using (OleDbConnection con = new OleDbConnection(_connectionString))
{
//open connection
con.Open();
}
return result;
}
Now, we'll create an instance of OleDbCommand inside the instance of OleDbConnection.
public int AddProduct(string productName, decimal price, int quantity, double weightInGrams, int daysSupplyLeft, int reorderWhenQuantityReaches, bool orderMore = false)
{
int result = 0;
string sqlText = "INSERT INTO Product (ProductName, Price, Quantity, [Weight(g)], DaysSupplyLeft, ReorderWhenQuantityReaches, OrderMore) VALUES(?, ?, ?, ?, ?, ?, ?)";
using (OleDbConnection con = new OleDbConnection(_connectionString))
{
//open connection
con.Open();
using (OleDbCommand sqlCmd = new OleDbCommand(sqlText, con))
{
}
}
return result;
}
Let's look at how to declare a parameter. First, create a new instance of OleDbParameter.
OleDbParameter paramProductName = new OleDbParameter();
Now, set the properties. It's important to set the correct data types for the database columns. While "ParameterName" isn't used by OleDbCommand, I've chosen to set the property because it makes debugging easier. If the value isn't set the value will show as "Parameter1", "Parameter2", etc...
According to Set the field size (Access),
Long Integer — For integers that range from -2,147,483,648 to +2,147,483,647. Storage requirement is four bytes.
Data types for Access desktop databases
Currency: Monetary data, stored with 4 decimal places of precision. 8
bytes
Yes/No: Boolean (true/false) data; Access stores the numeric
value zero (0) for false, and -1 for true. 1 byte.
and according to Integral numeric types (C# reference)
int -2,147,483,648 to 2,147,483,647 Signed 32-bit integer System.Int32
We also need to look at OleDbType Enum
BigInt: A 64-bit signed integer (DBTYPE_I8). This maps to Int64.
Boolean: A Boolean value
Currency: A currency value...
Decimal:
Double: A floating-point number...
Integer: A 32-bit signed integer (DBTYPE_I4). This maps to Int32.
OleDbParameter paramProductName = new OleDbParameter();
paramProductName.OleDbType = OleDbType.VarChar;
paramProductName.ParameterName = "#productName";
paramProductName.Value = productName;
To add the property to our instance of OleDbCommand (sqlCmd), we do the following:
sqlCmd.Parameters.Add(paramProductName);
Here's what we have so far:
public int AddProduct(string productName, decimal price, int quantity, double weightInGrams, int daysSupplyLeft, int reorderWhenQuantityReaches, bool orderMore = false)
{
int result = 0;
string sqlText = "INSERT INTO Product (ProductName, Price, Quantity, [Weight(g)], DaysSupplyLeft, ReorderWhenQuantityReaches, OrderMore) VALUES(?, ?, ?, ?, ?, ?, ?)";
using (OleDbConnection con = new OleDbConnection(_connectionString))
{
//open connection
con.Open();
using (OleDbCommand sqlCmd = new OleDbCommand(sqlText, con))
{
OleDbParameter paramProductName = new OleDbParameter();
paramProductName.OleDbType = OleDbType.VarChar;
paramProductName.ParameterName = "#productName";
paramProductName.Value = productName;
//add parameter
sqlCmd.Parameters.Add(paramProductName);
}
}
return result;
}
Now, we can add the rest of the parameters, keeping in mind that they must be added in the order that they are used (referenced). Before we do, let's look at how to execute the SQL.
To execute the SQL:
result = sqlCmd.ExecuteNonQuery();
AddProduct:
public int AddProduct(string productName, decimal price, int quantity, double weightInGrams, int daysSupplyLeft, int reorderWhenQuantityReaches, bool orderMore = false)
{
int result = 0;
string sqlText = "INSERT INTO Product (ProductName, Price, Quantity, [Weight(g)], DaysSupplyLeft, ReorderWhenQuantityReaches, OrderMore) VALUES(?, ?, ?, ?, ?, ?, ?)";
using (OleDbConnection con = new OleDbConnection(_connectionString))
{
//open connection
con.Open();
using (OleDbCommand sqlCmd = new OleDbCommand(sqlText, con))
{
//ProductName
OleDbParameter paramProductName = new OleDbParameter();
paramProductName.OleDbType = OleDbType.VarChar;
paramProductName.ParameterName = "#productName";
paramProductName.Value = productName;
//add parameter
sqlCmd.Parameters.Add(paramProductName);
//Price
OleDbParameter paramPrice = new OleDbParameter();
paramPrice.OleDbType = OleDbType.Currency;
paramPrice.ParameterName = "#price";
paramPrice.Value = price;
//add parameter
sqlCmd.Parameters.Add(paramPrice);
//Quantity
OleDbParameter paramQuantity = new OleDbParameter();
paramQuantity.OleDbType = OleDbType.Integer;
paramQuantity.ParameterName = "#quantity";
paramQuantity.Value = quantity;
//add parameter
sqlCmd.Parameters.Add(paramQuantity);
//Weight(g)
OleDbParameter paramWeight = new OleDbParameter();
paramWeight.OleDbType = OleDbType.Double;
paramWeight.ParameterName = "#weight";
paramWeight.Value = weightInGrams;
//add parameter
sqlCmd.Parameters.Add(paramWeight);
//DaysSupplyLeft
OleDbParameter paramDaysSupplyLeft = new OleDbParameter();
paramDaysSupplyLeft.OleDbType = OleDbType.Integer;
paramDaysSupplyLeft.ParameterName = "#daysSupplyLeft";
paramDaysSupplyLeft.Value = daysSupplyLeft;
//add parameter
sqlCmd.Parameters.Add(paramDaysSupplyLeft);
//ReorderWhenSupplyReaches
OleDbParameter paramReorderWhenQuantityReaches = new OleDbParameter();
paramReorderWhenQuantityReaches.OleDbType = OleDbType.Integer;
paramReorderWhenQuantityReaches.ParameterName = "#reorderWhenQuantityReaches";
paramReorderWhenQuantityReaches.Value = reorderWhenQuantityReaches;
//add parameter
sqlCmd.Parameters.Add(paramReorderWhenQuantityReaches);
//orderMore
OleDbParameter paramOrderMore = new OleDbParameter();
paramOrderMore.OleDbType = OleDbType.Boolean;
paramOrderMore.ParameterName = "#orderMore";
paramOrderMore.Value = orderMore;
//add parameter
sqlCmd.Parameters.Add(paramOrderMore);
//execute command
result = sqlCmd.ExecuteNonQuery();
}
}
return result;
}
Now let's look at how to perform an update to the database table. Create a method named UpdateProductQuantity in "HelperAccess.cs"
public int UpdateProductQuantity(int id, int quantity)
{
}
Create the SQL:
string sqlText = "UPDATE Product SET Quantity = ? WHERE ID = ?";
The rest of the code is similar to the previous method, so I won't go over it again.
UpdateProductQuantity:
public int UpdateProductQuantity(int id, int quantity)
{
string sqlText = "UPDATE Product SET Quantity = ? WHERE ID = ?";
int result = 0;
using (OleDbConnection con = new OleDbConnection(_connectionString))
{
//open connection
con.Open();
using (OleDbCommand sqlCmd = new OleDbCommand(sqlText, con))
{
//Note: Parameters MUST be added in the order that they are used (referenced) since
//ParameterName isn't used by OleDbCommand. However, setting the
//ParameterName property can be useful for debugging
// Quantity
OleDbParameter paramQuantity = new OleDbParameter();
paramQuantity.OleDbType = OleDbType.Integer;
paramQuantity.ParameterName = "#quantity";
//calculate quantity remaining and set value
paramQuantity.Value = quantity;
//add parameter
sqlCmd.Parameters.Add(paramQuantity);
//ID
OleDbParameter paramId = new OleDbParameter();
paramId.OleDbType = OleDbType.Integer;
paramId.ParameterName = "#id";
paramId.Value = id;
//add parameter
sqlCmd.Parameters.Add(paramId);
//execute command
result = sqlCmd.ExecuteNonQuery();
}
}
return result;
}
How might we be able to use the code to update the quantity? If a customer makes a purchase, we'll want to update the quantity. One can get the current quantity, subtract the quantity purchased, and update the database. Then if the quantity falls below the quantity specified in "ReorderWhenQuantityReaches" in the Product table, we'll set the value of "OrderMore" to "Yes" and raise an event that subscribers can listen for. This can be used to update the OrderMore TextBox on the form.
Let's create a method to get the existing quantity from the Product table. Since this is part of the update process, we can open the connection once and then use that connection in our method that gets the quantity.
In "HelperAccess.cs", create a method named GetCurrentQuantity, add a variable to return, and add the return statement.
public int GetCurrentQuantity(OleDbConnection con, int id)
{
int currentQuantity = 0;
return currentQuantity;
}
Create the SQL query:
string sqlText = "SELECT quantity from Product where ID = ?";
Create an instance of OleDbCommand, and add the id parameter:
public int GetCurrentQuantity(OleDbConnection con, int id)
{
int currentQuantity = 0;
string sqlText = "SELECT quantity from Product where ID = ?";
using (OleDbCommand sqlCmd = new OleDbCommand(sqlText, con))
{
//ID
OleDbParameter paramId = new OleDbParameter();
paramId.OleDbType = OleDbType.Integer;
paramId.ParameterName = "#id";
paramId.Value = id;
//add parameter
sqlCmd.Parameters.Add(paramId);
}
return currentQuantity;
}
There are a number of ways to read a value from the database. We'll use OleDbDataReader. Create a new instance of OleDbReader and use it to get the value. The column can be either referenced by it's column name or relative position within the query. The index is zero based. Since we only specify one column, the index is zero.
GetCurrentQuantity:
public int GetCurrentQuantity(OleDbConnection con, int id)
{
int currentQuantity = 0;
string sqlText = "SELECT quantity from Product where ID = ?";
using (OleDbCommand sqlCmd = new OleDbCommand(sqlText, con))
{
//ID
OleDbParameter paramId = new OleDbParameter();
paramId.OleDbType = OleDbType.Integer;
paramId.ParameterName = "#id";
paramId.Value = id;
//add parameter
sqlCmd.Parameters.Add(paramId);
//get value
using (OleDbDataReader reader = sqlCmd.ExecuteReader())
{
while (reader.Read())
{
currentQuantity = (int)reader[0];
}
}
}
return currentQuantity;
}
Next, let's create a method that updates the value of "OrderMore" when the quantity changes. We'll get the current value of quantity. We also need to know what quantity "triggers" the value of "OrderMore" to be changed. This value is stored in "ReorderWhenQuantityReaches" so we'll need to retrieve that value and compare it to quantity. Once we complete our comparision, we can raise the event (OrderMoreValueUpdated) to notify subscribers whether the value of "OrderMore" is "Yes" or "No".
Before we continue, since we'll be raising an event in our method, let's see how to create an event that can be subscribed to. I won't go into too much detail about events, as one can find numerous articles/posts about them. I'll just show the code that needs to be added to make it work. Add the following code towards the top of "HelperAccess.cs":
public class HelperAccess
{
public delegate void EventHandlerOrderMoreValueUpdated(object sender, string e);
//event that subscribers can subscribe to
public event EventHandlerOrderMoreValueUpdated OrderMoreValueUpdated;
...
}
Here's how to raise the event:
//check if event has subscribers
if (OrderMoreValueUpdated != null)
{
//raise event
OrderMoreValueUpdated(this, "test");
}
Now we're ready to create our method UpdateOrderMore. Since I've already covered the different parts of the code, I'll just show the completed method.
UpdateOrderMore
public int UpdateOrderMore(OleDbConnection con, int id)
{
string sqlText = string.Empty;
int reorderWhenQuantityReaches = 0;
int result = 0;
//get Quantity
int currentQuantity = GetCurrentQuantity(con, id);
//get ReorderWhenQuantityReaches
sqlText = "SELECT ReorderWhenQuantityReaches from Product where ID = ?";
using (OleDbCommand sqlCmd = new OleDbCommand(sqlText, con))
{
//ParameterName isn't used by OleDbCommand. However, setting the
//ParameterName property can be useful for debugging
//ID
OleDbParameter paramId = new OleDbParameter();
paramId.OleDbType = OleDbType.Integer;
paramId.ParameterName = "#id";
paramId.Value = id;
//add parameter
sqlCmd.Parameters.Add(paramId);
//get value
using (OleDbDataReader reader = sqlCmd.ExecuteReader())
{
while (reader.Read())
{
reorderWhenQuantityReaches = (int)reader[0];
}
}
}
sqlText = "UPDATE Product SET OrderMore = ? WHERE ID = ?";
using (OleDbCommand sqlCmd = new OleDbCommand(sqlText, con))
{
//Note: Parameters MUST be added in the order that they are used (referenced) since
//ParameterName isn't used by OleDbCommand. However, setting the
//ParameterName property can be useful for debugging
//if quantity is below value specified in ReorderWhenQuantityReaches, then
//set OrderMore to true (Yes)
bool orderMoreVal = false;
string orderMoreValStr = string.Empty;
if (currentQuantity <= reorderWhenQuantityReaches)
{
orderMoreVal = true;
orderMoreValStr = "Yes";
}
else
{
orderMoreVal = false;
orderMoreValStr = "No";
}
//OrderMore
OleDbParameter paramOrderMore = new OleDbParameter();
paramOrderMore.OleDbType = OleDbType.Boolean;
paramOrderMore.ParameterName = "#orderMore";
paramOrderMore.Value = orderMoreVal;
//add parameter
sqlCmd.Parameters.Add(paramOrderMore);
//ID
OleDbParameter paramId = new OleDbParameter();
paramId.OleDbType = OleDbType.Integer;
paramId.ParameterName = "#id";
paramId.Value = id;
//add parameter
sqlCmd.Parameters.Add(paramId);
//execute command
result = sqlCmd.ExecuteNonQuery();
//check if event has subscribers
if (OrderMoreValueUpdated != null)
{
//raise event
OrderMoreValueUpdated(this, orderMoreValStr);
}
}
return result;
}
Finally, here's a method that incorporates the above code. As part of completing a sale, the current quantity needs to be reduced by the quantity that is being purchased. Then we'll check whether or not we need to order more of the product and report the result.
UpdateProductQuantityAfterPurchase
public int UpdateProductQuantityAfterPurchase(int id, int quantityPurchased)
{
string sqlText = "UPDATE Product SET Quantity = ? WHERE ID = ?";
int resultUpdateQuantity = 0;
using (OleDbConnection con = new OleDbConnection(_connectionString))
{
//open connection
con.Open();
//get current quantity
int currentQuantity = GetCurrentQuantity(con, id);
using (OleDbCommand sqlCmd = new OleDbCommand(sqlText, con))
{
//Note: Parameters MUST be added in the order that they are used (referenced) since
//ParameterName isn't used by OleDbCommand. However, setting the
//ParameterName property can be useful for debugging
// Quantity
OleDbParameter paramQuantity = new OleDbParameter();
paramQuantity.OleDbType = OleDbType.Integer;
paramQuantity.ParameterName = "#quantity";
//calculate quantity remaining and set value
paramQuantity.Value = currentQuantity - quantityPurchased;
//add parameter
sqlCmd.Parameters.Add(paramQuantity);
//ID
OleDbParameter paramId = new OleDbParameter();
paramId.OleDbType = OleDbType.Integer;
paramId.ParameterName = "#id";
paramId.Value = id;
//add parameter
sqlCmd.Parameters.Add(paramId);
//execute command
resultUpdateQuantity = sqlCmd.ExecuteNonQuery();
if (resultUpdateQuantity > 0)
{
System.Diagnostics.Debug.WriteLine("Info: " + resultUpdateQuantity + " row updated.");
}
}
//update OrderMore
int resultUpdateOrderMore = UpdateOrderMore(con, id);
}
return resultUpdateQuantity;
}
I can't add all of the code for the methods in "Helper.cs" because of the character limitation for a post, but the code is above. The layout of "Helper.cs" should look similar to the following ("..." is where the code goes):
HelperAccess.cs
public class HelperAccess
{
private string _databaseFilename = string.Empty;
private string _connectionString = string.Empty;
public delegate void EventHandlerOrderMoreValueUpdated(object sender, string e);
//event that subscribers can subscribe to
public event EventHandlerOrderMoreValueUpdated OrderMoreValueUpdated;
public HelperAccess(string databaseFilename)
{
_databaseFilename = databaseFilename;
_connectionString = String.Format("Provider = Microsoft.ACE.OLEDB.12.0; Data Source = {0};Persist Security Info=False;", _databaseFilename);
}
public int AddProduct(string productName, decimal price, int quantity, double weightInGrams, int daysSupplyLeft, int reorderWhenQuantityReaches, bool orderMore = false)
{
...
}
public int GetCurrentQuantity(OleDbConnection con, int id)
{
...
}
public int UpdateProductQuantity(int id, int quantity)
{
...
}
public int UpdateProductQuantityAfterPurchase(int id, int quantityPurchased)
{
...
}
public int UpdateOrderMore(OleDbConnection con, int id)
{
...
}
}
Usage
For testing, I added the following to Form1:
The following TextBox controls were added to Form1:
name: textBoxId
name: textBoxProductName
name: textBoxPrice
name: textBoxQuantity
name: textBoxWeight
name: textBoxReorderWhenQuantityReaches
name: textBoxDaysSupplyLeft
name: textBoxOrderMore (ReadOnly: true)
Note: I also added a Label to the left of each TextBox.
Lastly, I added a Button (name: btnSave) to Form1.cs
Form1.cs:
public partial class Form1 : Form
{
private static string _databaseFilename = #"C:\Users\Test\Documents\Database1.accdb";
private HelperAccess _helperAccess = null;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//create new instance
_helperAccess = new HelperAccess(_databaseFilename);
//subscribe to event
_helperAccess.OrderMoreValueUpdated += _helperAccess_OrderMoreValueUpdated;
}
private void _helperAccess_OrderMoreValueUpdated(object sender, string e)
{
//needed for cross-threaded operations
if (textBoxOrderMore.InvokeRequired)
{
textBoxOrderMore.Invoke((MethodInvoker) delegate
{
textBoxOrderMore.Text = (string)e;
textBoxOrderMore.Refresh();
});
}
else
{
textBoxOrderMore.Text = (string)e;
textBoxOrderMore.Refresh();
}
}
private void AddProduct()
{
//convert price
decimal price = 0;
Decimal.TryParse(textBoxPrice.Text, out price);
//convert quantity
int quantity = 0;
Int32.TryParse(textBoxQuantity.Text, out quantity);
//convert weight
double weight = 0;
Double.TryParse(textBoxWeight.Text, out weight);
//convert days supply left
int daysSupplyLeft = 0;
Int32.TryParse(textBoxDaysSupplyLeft.Text, out daysSupplyLeft);
//convert reorder when quantity reaches
int reorderWhenQuantityReaches = 0;
Int32.TryParse(textBoxReorderWhenQuantityReaches.Text, out reorderWhenQuantityReaches);
//add product to database
int result = _helperAccess.AddProduct(textBoxProductName.Text, price, quantity, weight, daysSupplyLeft, reorderWhenQuantityReaches);
}
private void btnSave_Click(object sender, EventArgs e)
{
AddProduct();
}
}
To use UpdateProductQuantityAfterPurchase:
_helperAccess.UpdateProductQuantityAfterPurchase(id, 1);
I need select the maximum ID of PolygonId column. I save my data like this
string sql = "create table Polygons (PolygonId int, PointId int, X double, Y double)";
// Выполнение нашей команды
using (SQLiteCommand command = new SQLiteCommand(sql, m_dbConnection))
{
command.ExecuteNonQuery();
}
int pointId = 1;
for (int i = 0; i < listOfCustomPolygons.Count; i++)
for (int j = 0; j < listOfCustomPolygons[i].listOfVertexes.Count; j++)
{
string strSQL =
string.Format("INSERT INTO Polygons (PolygonId,PointId,X,Y) Values ('{0}','{1}','{2}','{3}')",
i+1,pointId,listOfCustomPolygons[i].listOfVertexes[j].X,
listOfCustomPolygons[i].listOfVertexes[j].Y );
pointId++;
using (SQLiteCommand insertCommand = new SQLiteCommand(strSQL, m_dbConnection))
{
insertCommand.ExecuteNonQuery();
}
}
After this I want select the max value from table Polygons and column PolygonId, but I got an IndexOutOfRangeException. How a can solve this problem?
using (SQLiteConnection connection = new SQLiteConnection("Data Source=" + openFileDialog.FileName + ";Version=3;"))
{
connection.Open();
string selectMaxId = "Select Max(PolygonId) From Polygons";
string selectQuery = "Select * From Polygons";
SQLiteCommand selectMaxCmd = new SQLiteCommand(selectMaxId,connection);
SQLiteDataReader dataReader = selectMaxCmd.ExecuteReader();
int maxId = Convert.ToInt32(dataReader["Select Max(PolygonId) From Polygons"]); // This is don't work! Why?
I found out the solution! It should look like
string selectMaxId = "Select Max(PolygonId) From Polygons";
SQLiteCommand selectMaxCmd = new SQLiteCommand(selectMaxId,connection);
object val = selectMaxCmd.ExecuteScalar();
int maxId = int.Parse(val.ToString());
I hope it can help somebody who face with similar problem)
First of all don't create table every time you run your code :) But you probably know that
You type like this:
int maxId = Convert.ToInt32(dataReader["Select Max(PolygonId) From Polygons"]);
Try this:
string selectMaxId = "Select Max(PolygonId) From Polygons";
SQLiteCommand selectMaxCmd = new SQLiteCommand(selectMaxId,connection);
SQLiteDataReader dataReader = selectMaxCmd.ExecuteReader();
int maxID = -1;
while(dataReader.read())
{
maxID = (int)dataReader.GetValue(0);
}
//This Works for me in WPF C#:
int MaxNum=0;
sqliteCon.Open();
string Query = "SELECT MAX(Promo_No)FROM Promo_File";
SQLiteCommand createCommand = new SQLiteCommand(Query, sqliteCon);
SQLiteDataReader DR = createCommand.ExecuteReader();
while (DR.Read())
{
MaxNum = DR.GetInt16(0);
}
sqliteCon.Close();
I had the same problem!
You have to learn the difference method of SQLiteCommand.
1.SQLiteCommand.ExecuteReader(). Get a SqlDataReader.
2.SQLiteCommand.ExecuteScalar(). Get a single value from the database.
Microsoft Doc:
cmd.CommandText = "SELECT COUNT(*) FROM dbo.region";
Int32 count = (Int32) cmd.ExecuteScalar();
I've got a DataGridView that has 2 columns - product name and the quantity of it. So I grab each row in a foreach loop and calculate the price of it. I managed to do that but I can't seems to figure out how to store ALL the calculated rows into a single variable and insert them into a database.
This is what I have so far:
string cMedication = string.Empty;
string cQuantity = string.Empty;
string cAppointment = string.Empty;
foreach (DataGridViewRow row in this.dataPrescription.Rows)
{
cMedication = row.Cells[0].Value.ToString();
cQuantity = row.Cells[1].Value.ToString();
cAppointment = txtAppointmentID.Text;
if (cAppointment == "NO APPOINTMENT HAS BEEN MADE")
{
MessageBox.Show("Please make an appointment first at the Nurse counter", "WARNING");
}
else
{
//this.savePrescription(cMedication, cQuantity, cAppointment);
string strConnectionString = ConfigurationManager.ConnectionStrings["HConnection"].ConnectionString;
string strCalc = "SELECT medicationPrice FROM MEDICATION WHERE medicationName= ('" + cMedication + "')";
using (SqlConnection connection = new SqlConnection(strConnectionString))
{
using (SqlCommand cmdCalc = new SqlCommand(strCalc, connection))
{
connection.Open();
SqlDataReader readPrice = cmdCalc.ExecuteReader();
if (readPrice.Read())
{
string getPrice = readPrice["medicationPrice"].ToString();
double doublePrice = Convert.ToDouble(getPrice);
double doubleQuantity = Convert.ToDouble(cQuantity);
double result = doublePrice * doubleQuantity;
string answer = result.ToString();
//insert TOTAL amount to database below
}
readPrice.Close();
connection.Close();
}
}
}
}
If you're doing this kind of thing a lot then I would use some kind of ORM like Entity Framework (or write your own). Then you would just load / create entites and save them.
If that's overkill for what you're doing then you could build up an insert statement and execute it, much like you've done to query the medication price. Only as I've mentioned in the comment, use SqlParameters instead of string concatenation to avoid possible sql injection attacks.
Something like this (untested).
var builder = new StringBuilder("INSERT INTO MedicationLine (MedicationName, Quantity, Price) VALUES ");
int i = 0;
var parameters = new List<SqlParameter>();
foreach (DataGridViewRow row in this.dataPrescription.Rows)
{
string cAppointment = txtAppointmentID.Text;
if (cAppointment == "NO APPOINTMENT HAS BEEN MADE")
{
MessageBox.Show("Please make an appointment first at the Nurse counter", "WARNING");
return;
}
string cMedication = row.Cells[0].Value.ToString();
string cQuantity = row.Cells[1].Value.ToString();
i++;
string strConnectionString = ConfigurationManager.ConnectionStrings["HConnection"].ConnectionString;
string strCalc = "SELECT medicationPrice FROM MEDICATION WHERE medicationName = #medicationName";
using (SqlConnection connection = new SqlConnection(strConnectionString))
{
using (SqlCommand cmdCalc = new SqlCommand(strCalc, connection))
{
command.Parameters.Add(new SqlParameter("medicationName", cMedication);
connection.Open();
SqlDataReader readPrice = cmdCalc.ExecuteReader();
if (readPrice.Read())
{
string getPrice = readPrice["medicationPrice"].ToString();
double doublePrice = Convert.ToDouble(getPrice);
double doubleQuantity = Convert.ToDouble(cQuantity);
builder.AppendLine();
builder.Append("(";
builder.Append("#Name");
builder.Append(i);
builder.Append("#Qty");
builder.Append(i);
builder.Append("#Price");
builder.Append(i);
builder.Append("),";
parameters.Add(new SqlParameter("Name" + i.ToString(), medicationName);
parameters.Add(new SqlParameter("Qty" + i.ToString(), doubleQuantity);
parameters.Add(new SqlParameter("Price" + i.ToString(), doublePrice);
}
readPrice.Close();
connection.Close();
}
}
}
The idea is to end up with something like:
INSERT INTO MedicationLine (MedicationName, Quantity, Price) VALUES
(#Name1, #Qty1, #Price1),
(#Name2, #Qty2, #Price2),
(#Name3, #Qty3, #Price3),
...
Then execute it. Don't forget to trim the trailing comma.
using (var connection = new SqlConnection(strConnectionString))
{
using (var command = new SqlCommand(builder.ToString().TrimEnd(','), connection))
{
command.Parameters.AddRange(parameters.ToArray());
connection.Open();
int recordsAffected = command.ExecuteNonQuery();
}
}
**Disclaimer
Syntax may be wrong as done without an IDE!
When calling the same query method twice in a session of the app, I get "DBCommandExcept"
As an experiment, I decided to dispose of the connection object at the end of the method, to see if that was the problem.
I no longer get the DBCommandExcept err msg, but instead get, "the connectionstring property has not been initialized."
IOW, it's sort of a Catch-22 situation at the moment. The pertinent code is:
string query = "SELECT Bla FROM Blah";
SqlCeCommand cmd = new SqlCeCommand(query);
cmd.CommandType = CommandType.Text;
SqlCeConnection conn = dbconn.GetConnection();
cmd.CommandType = CommandType.Text;//probably unnecessary
cmd.Connection = conn;
SqlCeDataReader myReader = cmd.ExecuteReader(CommandBehavior.SingleRow);
try
{
if (myReader.Read())
{
itemID = myReader.GetString(ITEMID_INDEX);
packSize = myReader.GetString(PACKSIZE_INDEX);
recordFound = true;
}
}
catch (Exception ex)
{
RRDR.LogMsgs.Append(string.Format("Exception in PopulateControlsIfVendorItemsFound(): {0}", ex.Message));
}
finally
{
myReader.Close();
//if (null != conn)
//{
// conn.Dispose();
//}
}
// Re: the commented-out block above: When it is active, the DBCommandExcept problem is not seen; however, I then get, "the connectionstring property has not been initialized"
I think the only non-SQL-CE-standard bit above is the dbConn.GetConnection(). Here's some of that code:
SqlCeConnection objCon = null;
. . .
public SqlCeConnection GetConnection()
{
return objCon;
}
private DBConnection() // class constructor
{
try
{
. . .
objCon = new SqlCeConnection(conStr);
objCon.Open();
. . .
Again, the error (either one, whichever one I "choose" to have) is seen only the second time through this method during one run of the app. The first time works fine.
UPDATE
I added the code below, and the comments tell the tale of woe:
// With conn check only, still get two consecutive DBCommandExcepts
// With cmd check only, still get two consecutive DBCommandExcepts
// With both, still get two consecutive DBCommandExcepts; IOW, all have the same effect
if (null != conn)
{
conn.Close();
}
if (null != cmd)
{
cmd.Dispose();
}
UPDATE 2
Based on unicron's suggestion, I tried using "using."
In two of the three cases (SqlCeCommand and SqlCeDataReader), converting to "using" made no diff; in the other one (SqlCeConnection), it raised the err msgs, "The ConnectionString property has not been initialized."
Still, though, the code is cleaner with the two usings, so thanks for that nudge in the best practices direction.
Here's what it looks like now:
private bool PopulateControlsIfPlatypusItemsFound()
{
const int ITEMID_INDEX = 0;
const int PACKSIZE_INDEX = 1;
bool recordFound = false;
try
{
string PlatypusId = txtPlatypus.Text.ToString().Trim();
string PlatypusItemId = txtUPC.Text.ToString().Trim();
string itemID = string.Empty;
string packSize = string.Empty;
string query = string.Format("SELECT ItemID, PackSize FROM PlatypusItems WHERE PlatypusID = {0} AND PlatypusItemID = {1}", PlatypusId, PlatypusItemId);
using (SqlCeCommand cmd = new SqlCeCommand(query))
{
cmd.CommandType = CommandType.Text;
SqlCeConnection conn = dbconn.GetConnection();
if ((null != conn) && (!conn.State.Equals(ConnectionState.Open)))
{
conn.Open();
TTBT.LogMsgs.Append("Connection opened");
}
cmd.CommandType = CommandType.Text;//probably unnecessary
cmd.Connection = conn;
using (SqlCeDataReader myReader = cmd.ExecuteReader(CommandBehavior.SingleRow))
{
if (myReader.Read())
{
itemID = myReader.GetString(ITEMID_INDEX);
packSize = myReader.GetString(PACKSIZE_INDEX);
recordFound = true;
}
}
txtID.Text = itemID;
txtSize.Text = packSize;
return recordFound;
}
}
catch (Exception ex)
{
TTBT.LogMsgs.Append(string.Format("Exception in PopulateControlsIfPlatypusItemsFound: {0} - {1}\r\n", ex.Message, ex.InnerException));
return recordFound;
}
}
UPDATE 3
I've come even closer to normalcy by replacing the custom connection code with the generic sort, adding another "using" to the mix:
private bool PopulateControlsIfVendorItemsFound()
{
const int ITEMID_INDEX = 0;
const int PACKSIZE_INDEX = 1;
bool recordFound = false;
DUCKBILL.LogMsgs.Append("Made it into frmEntry.PopulateControlsIfVendorItemsFound()\r\n");
try
{
string vendorId = txtVendor.Text.ToString().Trim();
string vendorItemId = txtUPC.Text.ToString().Trim();
string itemID = string.Empty;
string packSize = string.Empty;
if ( dbconn.isValidTable( "VendorItems" ) == -1 )
{
DUCKBILL.LogMsgs.Append("VendorItems not a valid table");//do not see this msg; good! VendorItems is seen as valid...
return false;
}
string query = string.Format("SELECT ItemID, PackSize FROM VendorItems WHERE VendorID = {0} AND VendorItemID = {1}", vendorId, vendorItemId);
using (SqlCeCommand cmd = new SqlCeCommand(query))
{
cmd.CommandType = CommandType.Text;
using (SqlCeConnection conn = new SqlCeConnection())
{
string filename = "\\badPlace2B\\CCRDB.SDF";
conn.ConnectionString = string.Format("Data Source = {0}", filename);
cmd.CommandType = CommandType.Text;//probably unnecessary/moot
cmd.Connection = conn;
conn.Open();
using (SqlCeDataReader myReader = cmd.ExecuteReader(CommandBehavior.SingleRow))
{
if (myReader.Read())
{
itemID = myReader.GetString(ITEMID_INDEX);
packSize = myReader.GetString(PACKSIZE_INDEX);
recordFound = true;
}
}
}
txtID.Text = itemID;
txtSize.Text = packSize;
return recordFound;
}
}
catch (Exception ex)
{
DUCKBILL.LogMsgs.Append(string.Format("Exception in PopulateControlsIfVendorItemsFound: {0} - {1}\r\n", ex.Message, ex.InnerException));
return recordFound;
}
}
...yet I still get "DBCommandExcept"...
As to "stop futzing around with opening the connection," isn't it necessary to do so? How could/should the code above be different?
UPDATE 4
What is even more bizarre is that now my debug log file has stopped being written. I have been writing it out both in the global exception handler AND in the main form's Closed event(), and it always has (until now) at least a few entries, but within the last couple of updates to the code, it is no longer being written...????
Both places global exception handler and main form's Closed event(), the code is like so:
public static bool inDebugMode = true;
. . .
if (CCR.inDebugMode)
{
DateTime dt = DateTime.Now;
string timeAsStr = string.Format("{0}_{1}_{2}_{3}.txt", dt.Hour, dt.Minute, dt.Second, dt.Millisecond);
using (StreamWriter file = new StreamWriter(timeAsStr))
{
// If the app closes normally, this is how the file is written; if it doesn't,
// (it crashed) it's written in PDAClient.ExceptionHandler()
file.WriteLine(SSCS.LogMsgs.ToString());
}
}
Since you are making several calls to a database file (that isn't going to change), I'd start out by defining your connection string and your SQL statements at the top of your class as global values:
private const int ITEMID_INDEX = 0;
private const int PACKSIZE_INDEX = 1;
private const string SQL_CONN_STR = "Data Source=\\badPlace2B\\CCRDB.SDF";
private const string SQL_GET_VENDOR_ITEMS = "SELECT ItemID, PackSize " +
"FROM VendorItems " +
"WHERE VendorID=#VendorID AND VendorItemID=#VendorItemID";
These never change, so there is no reason to define them again each time you call your routine.
Personally, I do not like inserting values into SQL statements, like you have shown. Rather, try to use Parameters.
To use Parameters, you'll need to look into your database to see what type of columns VendorID and VendorItemID are. My guess is that they are both int values, but these could be GUID like values, requiring VarChar type strings. If these are strings, you should write down what sizes the columns are defined as.
For example: Below, my Serial_Number column is the SqlDbType.NVarChar and the size is 50. An SqlCeParameter for this column would be:
cmd.Parameters.Add("#Serial_Number", SqlDbType.NVarChar, 50).Value = txtSerial_Number.Text.Trim();
Since I did not know what type of data you use, I created an enumerated type to show how each method would be used. If you do not have access to the table's design, the last resort is "AddWithValue" (I personally hate that one, because it makes me look like I don't know what my database has inside).
enum ParamStyle { AddWithValue, AddIntegers, AddVarChar }
To use this enumerated type, I modified the signature of your method to pass in that value:
private bool PopulateControlsIfVendorItemsFound(ParamStyle style) {
Obviously, you will not need this, because you should know what technique you are going to be coding with.
I wasn't able to figure out what your dbconn object was. Initially, I thought this was your SqlCeConnection, but that does not have an isValidTable method, so I just commented it out:
//if (dbconn.isValidTable("VendorItems") == -1) {
// DUCKBILL.LogMsgs.Append("VendorItems not a valid table");//do not see this msg; good! VendorItems is seen as valid...
// return false;
//}
Speaking of SqlCeConnection...
I combined your SqlCeCommand instance with your SqlCeConnection instance. Less code typically means fewer errors:
using (var cmd = new SqlCeCommand(SQL_GET_VENDOR_ITEMS, new SqlCeConnection(SQL_CONN_STR))) {
The CommandType, by default, is CommandType.Text, so this line is unnecessary:
// cmd.CommandType = CommandType.Text; (this is the default)
I moved most of your variable reading outside of the try/catch routine, as none of that should ever cause an exception to be generated.
Also, I used the more targeted SqlCeException instead of the general Exception. The only thing that could fail in the block is something SqlCe related, and the SqlCeException will give you better/more specific error messages than the general Exception object will.
} catch (SqlCeException err) {
So, what does it look like all put together?
Code:
enum ParamStyle { AddWithValue, AddIntegers, AddVarChar }
private const int ITEMID_INDEX = 0;
private const int PACKSIZE_INDEX = 1;
private const string SQL_CONN_STR = "Data Source=\\badPlace2B\\CCRDB.SDF";
private const string SQL_GET_VENDOR_ITEMS = "SELECT ItemID, PackSize FROM VendorItems WHERE VendorID=#VendorID AND VendorItemID=#VendorItemID";
private bool PopulateControlsIfVendorItemsFound(ParamStyle style) {
bool recordFound = false;
//DUCKBILL.LogMsgs.Append("Made it into frmEntry.PopulateControlsIfVendorItemsFound()\r\n");
string itemID = null;
string packSize = null;
//string vendorId = txtVendor.Text.Trim();
//string vendorItemId = txtUPC.Text.Trim();
//string query = string.Format("SELECT ItemID, PackSize FROM VendorItems WHERE VendorID = {0} AND VendorItemID = {1}", vendorId, vendorItemId);
//if (dbconn.isValidTable("VendorItems") == -1) {
// DUCKBILL.LogMsgs.Append("VendorItems not a valid table");//do not see this msg; good! VendorItems is seen as valid...
// return false;
//}
using (var cmd = new SqlCeCommand(SQL_GET_VENDOR_ITEMS, new SqlCeConnection(SQL_CONN_STR))) {
// cmd.CommandType = CommandType.Text; (this is the default)
if (style == ParamStyle.AddIntegers) { // Adding Integers:
cmd.Parameters.Add("#VendorID", SqlDbType.Int).Value = Convert.ToInt32(txtVendor.Text.Trim());
cmd.Parameters.Add("#VendorItemID", SqlDbType.Int).Value = Convert.ToInt32(txtUPC.Text.Trim());
} else if (style == ParamStyle.AddVarChar) { // Adding String Values
// NOTE: Here, you should look in your database table and
// use the size you defined for your VendorID and VendorItemID columns.
cmd.Parameters.Add("#VendorID", SqlDbType.VarChar, 25).Value = txtVendor.Text.Trim();
cmd.Parameters.Add("#VendorItemID", SqlDbType.VarChar, 50).Value = txtUPC.Text.Trim();
} else if (style == ParamStyle.AddWithValue) { // Adding as Objects (only if you don't know what the data types are)
cmd.Parameters.AddWithValue("#VendorID", txtVendor.Text.Trim());
cmd.Parameters.AddWithValue("#VendorItemID", txtUPC.Text.Trim());
}
try {
cmd.Connection.Open();
using (var myReader = cmd.ExecuteReader(CommandBehavior.SingleRow)) {
if (myReader.Read()) {
itemID = myReader.GetString(ITEMID_INDEX);
packSize = myReader.GetString(PACKSIZE_INDEX);
recordFound = true;
}
}
} catch (SqlCeException err) {
//DUCKBILL.LogMsgs.Append(string.Format("Exception in PopulateControlsIfVendorItemsFound: {0}\r\n", err.Message));
// (I never return from a 'catch' statement) return recordFound;
} finally {
if (cmd.Connection.State == ConnectionState.Open) {
cmd.Connection.Close();
}
}
}
if (recordFound) { // set these last, and set them OUTSIDE of the try/catch block
txtID.Text = itemID;
txtSize.Text = packSize;
}
return recordFound;
}
Happy Coding!
I have used the store procedure in MS-Sql for inserting for particular page it is working fine but I also need the same store procedure for other page it is finding me the error.
protected void btnsubmitt_Click(object sender, EventArgs e)
{
ArrayList arParameters = ReturnParameter();
DataSet dsInsertProfile = objadmin.GetGridData(arParameters, objconstant.sSP_INSERT_PROFILE);
if (int.Parse(dsInsertProfile.Tables[0].Rows[0].ItemArray[0].ToString()) == 0)
{
pnlProfile.Visible = false;
pnlThank.Visible = true;
lblThank.Text = "Your profile have been successfully saved.";
}
else
{
lblThank.Text = "Your profile is not saved, please try again later.";
}
}
public ArrayList ReturnParameter()
{
Int64 email = Convert.ToInt64(txtemail.Text.ToString());
ArrayList arSample = new ArrayList();
Object[] c_email_id = new Object[3] { "#strEmailID", "varchar", email};
arSample.Add(c_email_id);
return arSample;
}
public DataSet GetGridData(ArrayList dbArray, string sSpName)
{
DataSet dsDataSet = new DataSet();
dsDataSet = datamanager.GetGridData(dbArray, sSpName);
return dsDataSet;
}
public static DataSet GetGridData(ArrayList dbArray, string sSpName)
{
DataSet dsDataSet = new DataSet();
SqlConnection cn = createConnection();
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = sSpName;
object objPrMtrName;
object objSqlType;
object objPrMtrVal;
int i;
for (i = 0; i < dbArray.Count; i++)
{
objPrMtrName = ((object[])(dbArray[i]))[0];
objSqlType = ((object[])(dbArray[i]))[1];
objPrMtrVal = ((object[])(dbArray[i]))[2];
cmd.Parameters.Add(objPrMtrName.ToString(), GetSqlDataType(objSqlType.ToString())).Value = objPrMtrVal;
}
cmd.Connection = cn;
try
{
SqlDataAdapter adp = new SqlDataAdapter(cmd);
adp.Fill(dsDataSet);
return dsDataSet;
}
catch (Exception ex)
{
throw ex;
}
finally
{
cn.Close();
cn.Dispose();
}
}
Mystore procedure
ALTER Procedure [dbo].[spInsert_profile]
( #strEmailID varchar(200)
)
AS
BEGIN
DECLARE #intEmail INT
SET #intEmail = (SELECT COUNT(*) FROM gdt_Users WHERE [c_email_id]=#strEmailID)
IF #intEmail = 0
BEGIN
Insert into gdt_Users([c_email_id],[d_modified_dttm],[d_created_dttm])values(#strEmailID,GETDATE(),GETDATE())
SELECT #intEmail
END
ELSE
BEGIN
SELECT #intEmail
END
END
Here, I was facing a problem. It was throwing an exception
ERROR: Failed to convert parameter value from string to Int64
So, I've add this code
Int64 email = Convert.ToInt64(txtemail.Text.ToString());
in arraylist returnparameter() method. Then, it threw that exception
ERROR: Input string was not in correct format
How can I solve this? Can you help me?
In your code I can find Convert.ToInt64 only at one place which is
Int64 email = Convert.ToInt64(txtemail.Text.ToString());
You would not be passing valid number to Convert.ToInt64( as txtemail will have string not a number. You should give a number in txtemail or use the correct textbox for converting the string to int64.
Keep in mind Convert.ToInt64( converts string to number only if the
string is number.
Your stored procedure expects varchar(200) which is equivalent to String of length 200, so email should be string and not number. E.g. yourname#someemail.com is a string. Am just curious, what makes you think is int64. It should be
string email = txtemail.Text;
Your code down here indicates email should be string by using "varchar"
string email = txtemail.Text; //change your code to this
ArrayList arSample = new ArrayList();
Object[] c_email_id = new Object[3] { "#strEmailID", "varchar", email};