Combobox doubles the value when populated by a database - c#

Im using C# Winforms and SQL Server as my database.
In my Combobox_Leave Event, it will populate other Combobox with the text value of the previous
This is one of my Combobox_Leave Event, it is similar with other Combobox
private void cmbPItem_Leave(object sender, EventArgs e)
{
using (SqlConnection conn = new SqlConnection(#"Server=" + ip + "," + port + "; Database=records; User ID=" + sqlid + "; Password=" + sqlpass + ""))
{
conn.Open();
using (SqlDataAdapter sda = new SqlDataAdapter(#"SELECT DISTINCT [Brand]
FROM [dbo].[products] WHERE Item LIKE '" + cmbPItem.Text + "'", conn))
{
DataTable dt = new DataTable();
sda.Fill(dt);
if (dt.Rows.Count != 0)
{
cmbPBrand.Items.Clear();
for (int b = 0; b < dt.Rows.Count; b++)
{
cmbPBrand.Items.Add(dt.Rows[b][0].ToString());
}
}
}
using (SqlDataAdapter sda = new SqlDataAdapter(#"SELECT DISTINCT [Manufacturer]
FROM [dbo].[products] WHERE Item LIKE '" + cmbPItem.Text + "'", conn))
{
DataTable dt = new DataTable();
sda.Fill(dt);
if (dt.Rows.Count != 0)
{
cmbPMan.Items.Clear();
for (int m = 0; m < dt.Rows.Count; m++)
{
cmbPMan.Items.Add(dt.Rows[m][0].ToString());
}
}
}
using (SqlDataAdapter sda = new SqlDataAdapter(#"SELECT DISTINCT [Car]
FROM [dbo].[products] WHERE Item LIKE '" + cmbPItem.Text + "'", conn))
{
DataTable dt = new DataTable();
sda.Fill(dt);
if (dt.Rows.Count != 0)
{
cmbPCar.Items.Clear();
for (int i = 0; i < dt.Rows.Count; i++)
{
cmbPCar.Items.Add(dt.Rows[i][0].ToString());
}
}
}
using (SqlDataAdapter sda = new SqlDataAdapter(#"SELECT DISTINCT [Year]
FROM [dbo].[products] WHERE Item LIKE '" + cmbPItem.Text + "'", conn))
{
DataTable dt = new DataTable();
sda.Fill(dt);
if (dt.Rows.Count != 0)
{
cmbPYr.Items.Clear();
for (int y = 0; y < dt.Rows.Count; y++)
{
cmbPYr.Items.Add(dt.Rows[y][0].ToString());
}
}
}
conn.Close();
}
}
And the output is like this
When the user click on the second Combox, in this example the cmbPBrand. It populates the Combobx with duplicated values. But when the user click another Combobox, not choosing any entry from cmbPBrand. The values are not duplicated.
Another example of duplicated values.
In this the cmbPYr is clicked after the selection from the cmbPItem. It duplicates the DISTINCT values.
Note that this happens when the user CLICK the second Combobox. And Im using Leave Event for my Combobox.
I also tried adding MouseClick and MouseDown and Enter and SelectedIndexChanged Events. But it still duplicate the values.
EDIT:
When using the query
SELECT DISTINCT Brand,Manufacturer,Car,Year FROM [dbo].[products] WHERE Item LIKE 'BRAKE PADS'
Where cmbPItem.Text, for example, is BRAKE PADS.
It will query almost 675 rows.
EDIT: As for Kevin suggestion. This is code is for Brand ComboBox only, but it still show duplicate values.
private void cmbProd_Enter(object sender, EventArgs e) {
itemValue(cmbPItem.Text); }
private void itemValue(string sitem) {
getBrand(sitem); }
private void getBrand(string sitem) {
using (SqlCommand cmd = new SqlCommand(#"SELECT DISTINCT [Brand] FROM [dbo].[products] WHERE Item = #Item"))
{
cmd.Parameters.Add(new SqlParameter("Item", sitem));
populateBrand(cmbPBrand, cmd);
} }
private void populateBrand(ComboBox cmb, SqlCommand cmd) {
using (SqlConnection conn = new SqlConnection(#"Server=" + ip + "," + port + "; Database=records; User ID=" + sqlid + "; Password=" + sqlpass + ""))
{
using (SqlDataAdapter sda = new SqlDataAdapter(cmd))
{
cmd.Connection = conn;
DataTable dt = new DataTable();
sda.Fill(dt);
if (dt.Rows.Count != 0)
{
cmb.Items.Clear();
for (int b = 0; b < dt.Rows.Count; b++)
{
cmb.Items.Add(dt.Rows[b][0].ToString());
}
}
conn.Close();
}
} }

I'm going to suggest tackling the problem from a slightly different angle.
First up, you definitely should think about changing those queries to be parameterized. Like I commented, any time you put raw input directly into a SQL statement, you're opening the door to SQL Injection Attacks. Even if it's not based on user input, it's still a bad habit to get into. Doing SQL parameterization isn't hard - it's just one extra line of code.
Next up: Refactor your code. If I understand you correctly, your code reads like:
Event X
{
13 or so lines to update Combo Box #1
13 or so lines to update Combo Box #2
13 or so lines to update Combo Box #3
13 or so lines to update Combo Box #4
}
Event Y
{
13 or so lines to update Combo Box #1
13 or so lines to update Combo Box #2
13 or so lines to update Combo Box #3
13 or so lines to update Combo Box #4
}
Event Z
{
13 or so lines to update Combo Box #1
13 or so lines to update Combo Box #2
13 or so lines to update Combo Box #3
13 or so lines to update Combo Box #4
}
Event ... etc
Do some googling and playing around with the Single Responsibility Principle (SRP) - it'll help you write cleaner, easier-to-debug code.
When that's said and done? Then you've got a good way of figuring out what the problem is: put some debug lines in your "UpdateBrandCombo()" function - the only place where the Brand combo box is updated (right now, you've got a problem in that any of those events might be updating the combo box, and you don't really have any good way of figuring out what's doing it.)
Something like:
Event X
{
UpdateCombosWithSearch(cmbPItem.Text);
}
// ... later on ...
private void UpdateCombosWithSearch(string searchTerm)
{
UpdateBrandCombo(searchTerm);
UpdateMfgCombo(searchTerm);
UpdateCarCombo(searchTerm);
}
private void UpdateBrandCombo(string searchTerm)
{
SqlCommand sqlCmd = new SqlCommand("select distinct car from dbo.products where Item like #item");
sqlCmd.Parameters.Add(new SqlParameter("item", searchTerm));
SetComboBoxUsingQuery(cmbPBrand, sqlCmd);
}
private void SetComboBoxUsingQuery(ComboBox cbx, SqlCommand sqlCmd)
{
cbx.Items.Clear();
// code to get a DataTable from the sqlCmd
// code to read the DataTable and add items to cbx
}
See the beauty? You don't have repetitive code. Your events all have one line: UpdateCombosWithSearch(). UpdateCombosWithSearch simply calls an Update on each combo box it needs to update. And each of those functions simply generate an SQL command and pass in which box is to be updated. The only function that even has any SQL code is the SetComboBoxUsingQuery() function.
So now you can add something like:
System.Diagnostics.Debug.WriteLine("Event logged by " + (new System.Diagnostics.StackTrace()).ToString());
... to one of those functions - so you can figure out just where/when/how your update code is being called.

Related

Filtering database results with 3 textBoxes

I am writing a program in which the user can filter results from a database by 3 textboxes, however, the results are not being filtered correctly, because if one box is left empty, it doesn't display anything
private void textBox1_TextChanged(object sender, EventArgs e)
{
con = new SQLiteConnection(cs);
con.Open();
if ((textBox2.Text==""||textBox.Text3=="")&&textBox1.Text!="")
{
adapt = new SQLiteAdapter("select data1, data2 from DataTable where data1 like '" + textBox1.Text + "%'", con);
dt = new DataTable();
adapt.Fill(dt);
dataGridView1.Source = dt;
}
else if(textBox1.Text !="")
{
adapt = new SQLiteAdapter("select data1, data2 from DataTable where data1 like '" + textBox1.Text + "%' and data2 like '" + textBox2.Text + "%' and substr(data2,-2) like '" + textBox3.Text +"'", con);
dt = new DataTable();
adapt.Fill(dt);
dataGridView1.Source = dt;
}
con.close();
}
That is the code that I am using on one of the textboxes, for the other two it look almost the same, except I change the if clause conditions.
Do I have to write 9 different clauses for each textbox, so that I cover all the options? Is there a right way?
I would parameterize the query to prevent sql injection and use the IFNULL function to help you. This way you have one query to cover all scenarios. If any textbox is empty, the LIKE clause for that item will basically not filter anything out:
string qry = #"SELECT
data1,
data2
FROM DataTable
WHERE
data1 LIKE IFNULL(#data1, data1) AND
data2 LIKE IFNULL(#data2, data2) AND
SUBSTR(data2, -2) LIKE IFNULL(#data3, data3)";
To create the parameters get the textbox values, set the parameter value to null if the textbox is empty. Do this for all 3 textboxes:
string data1 = null;
if(!string.IsNullOrWhiteSpace(textbox1.Text))
{
data1 = textbox1.Text + "%";
}
SqlLiteCommand cmd = new SqlLiteCommand(qry, con);
SqlLiteParameter parData1 = new SqlLiteParameter("#data1", (object)data1 ?? DBNull.Value);
cmd.Parameters.Add(parData1);
Now you can execute that command.
In the Constructor or Form_Load wire up the TextBoxes change events to the one handler:
textBox1.TextChanged += textBox1_TextChanged;
textBox2.TextChanged += textBox1_TextChanged;
textBox3.TextChanged += textBox1_TextChanged;
And build up the WHERE clause dynamically instead of 9 conditions:
con = new SQLiteConnection(cs);
con.Open();
StringBuilder sb = new StringBuilder();
sb.Append("select * from DataTable where ");
foreach (Control c in this.Controls) {
TextBox t = c as TextBox;
if (t != null) {
if (t.Length > 0) {
//In design-time set the TextBox Tag property to the SQL column name
sb.Append(t.Tag.ToString() + " like '" + t.Text + "%' and ");
}
}
}
string SQL = sb.ToString();
if (SQL.Length > 0) {
SQL = SQL.Substring(0, SQL.Length-5);
}
adapt = new SQLiteAdapter(SQL, con);
dt = new DataTable();
adapt.Fill(dt);
dataGridView1.Source = dt;
You should also use a Stored Procedure or pay
Parameterized commands.

How do I delete a row in my database using this method?

It's been a while since I've tried programming something. I've been practicing basic CRUD on a DGV and database. I've got my "ADD/CREATE" function working, but my delete doesn't seem to work.
here's a screenshot:
EDIT:
Posting code here; this is my working ADD/CREATE function:
private void btnAdd_Click(object sender, EventArgs e)
{
connectionstring = "server=" + server + ";" + "database=" + database +
";" + "uid=" + uid + ";" + "password=" + password + ";";
con = new MySqlConnection(connectionstring);
con.Open();
MySqlDataAdapter da = new MySqlDataAdapter("select * from testdatabase", con);
DataSet ds = new DataSet();
da.Fill(ds);
testTable1.DataSource = ds.Tables[0];
con.Close();
// now instead of these next 4 lines
DataRow row = ds.Tables[0].NewRow();
row[0] = tbID.Text;
row[1] = tbName.Text;
ds.Tables[0].Rows.Add(row);
// ds.Tables[0].Rows.RemoveAt(testTable1.CurrentCell.RowIndex);
// is what i used to delete
// what did i do wrong?
MySqlCommandBuilder cb = new MySqlCommandBuilder(da);
da.UpdateCommand = cb.GetUpdateCommand();
da.Update(ds);
((DataTable)testTable1.DataSource).AcceptChanges();
}
If you want to remove a row following a button click you could follow this pseudocode.
First you check if there is a current row selected in the grid,
If you found one then get the value from the cell where the primary
key for the database table is stored. (Here I assume that this cell
is the first one in the row and refers to the column ID in the
database table)
With that data open the connection, prepare the DELETE command with
parameters and call ExecuteNonQuery to phisycally remove the row from
the database table.
After that you could remove the current row from the grid and inform
the underlying datasource that the operation is
complete.(AcceptChanges)
private void btnDelete_Click(object sender, EventArgs e)
{
// No row to delete?
if(testTable1.CurrentRow == null)
return;
// Get the cell value with the primary key
int id = Convert.ToInt32(testTable1.CurrentRow.Cells[0].Value)
// Start the database work...
connectionstring = .....;
using(con = new MySqlConnection(connectionstring))
using(cmd = new MySqlCommand("DELETE FROM testdatabase where id = #id", con))
{
con.Open();
cmd.Parameters.AddWithValue("#id", id);
cmd.ExecuteNonQuery();
// Fix the grid removing the current row
testTable1.Rows.Remove(testTable1.CurrentRow);
// Fix the underlying datasource
((DataTable)testTable1.DataSource).AcceptChanges();
}
}

Pulling a SELECT query into a datatable and accessing it

I'm writing a small ASP.net C# web page and it keeps giving me an error stating:
There is no row at position 0.
I'm probably doing it wrong but here is some of my code:
string SqlQuery = "SELECT * ";
SqlQuery += " FROM main_list";
SqlQuery += " WHERE ID = #FindID";
SqlConnection conn = new SqlConnection("server=???;database=contacts;User
ID=???;Password=???;");
conn.Open();
SqlCommand SqlCmd = new SqlCommand(SqlQuery, conn);
SqlCmd.Parameters.Add("#FindID",searchID);
SqlDataAdapter da = new SqlDataAdapter(SqlCmd);
try {
da.Fill(dt);
fillData(p);
}
catch {
txtId.Text = "ERROR";
}
And FillData is the following:
protected void fillData(int pos) {
txtId.Text = dt.Rows[pos]["ID"].ToString();
txtCompany.Text = dt.Rows[pos]["Company"].ToString();
txtFirstName.Text = dt.Rows[pos]["First_Name"].ToString();
txtLastName.Text = dt.Rows[pos]["Last_Name"].ToString();
txtAddress1.Text = dt.Rows[pos]["Address1"].ToString();
txtAddress2.Text = dt.Rows[pos]["Address2"].ToString();
txtCity.Text = dt.Rows[pos]["City"].ToString();
txtState.Text = dt.Rows[pos]["State"].ToString();
txtZipCode.Text = dt.Rows[pos]["ZipCode"].ToString();
txtPhoneNum1.Text = dt.Rows[pos]["Phone_Num"].ToString();
txtPhoneNum2.Text = dt.Rows[pos]["Phone_Num2"].ToString();
txtFax.Text = dt.Rows[pos]["Fax_Num"].ToString();
txtEmail.Text = dt.Rows[pos]["Email"].ToString();
txtNotes.Text = dt.Rows[pos]["Notes"].ToString();
txtCategory.Text = dt.Rows[pos]["Category"].ToString();
txtSubCategory.Text = dt.Rows[pos]["SubCategory"].ToString();
txtDateAdded.Text = dt.Rows[pos]["DateAdded"].ToString();
txtDateModified.Text = dt.Rows[0]["DateModified"].ToString();
}
Here is the call that errors out:
protected void btnPrev_Click(object sender, EventArgs e) {
p--;
lblPage.Text = p.ToString();
fillData(p-1);
}
protected void btnNext_Click(object sender, EventArgs e) {
p++;
lblPage.Text = p.ToString();
fillData(p-1);
}
I'm trying to cycle thru the Rows[0] to Rows[1] or however many there is but it gives me the error about no row at position 0 or position 1. It only fills once and then errors out.
EDIT:
I'm trying to access the second row returned by the database after already accessing one row already. For example: Rows[0] is accessible fine but then when I try to read Rows[1] it errors and says it doesn't have a row in position 1. I can revise the code to return Rows[1] and it works but when I try to access Rows[0] it breaks. This is why I pass the variable (p) to fillData so it can show only that Rows value. Thanks!
EDIT 2: I believe it's because there is a postback that wipes the values retrieved by the database. Is there a way to get the database entries to stay even after a postback? If not I am guessing I will have to query the database every time.
The error message indicates there are no rows being returned by SQL. Are you sure there is data to be returned.
When you use dt.Rows[0] you are effectively saying "take the first row that comes back, and get a value from it." If the DataTable doesn't have any rows (i.e. your SQL query returns no matches), that's like saying "Here is a plate that contains no apples. Take the first apple and tell me what colour it is" - see? Doesn't make sense.
What you should do is check whether there are any rows before you try to read them...
if(dt.Rows.Count > 0)
{
// do stuff here.
}
Use Linq and a stored procedure it is much nicer
datacontext context = new datacontext();
var result = context.MyStoredProc(searchID).FirstOrDefault();
Try changing
SqlCmd.Parameters.Add("#FindID",searchID);
to
SqlCmd.Parameters.AddWithValue("#FindID",searchID);
Check your query on your database, make sure rows are actually being returned. Also, it's bad practice to put your query directly into your code like that, especially when using parameters. You might want to try something like this:
private Int32 CallStoredProcedure(Int32 FindId)
{
using (var dt = new DataTable())
{
using (var conn = new SqlConnection(ConnectionString))
{
using (var sqlCmd = new SqlCommand("SEL_StoredProcedure", conn))
{
using (var sda = new SqlDataAdapter(sqlCmd))
{
sqlCmd.CommandType = System.Data.CommandType.StoredProcedure;
sqlCmd.Parameters.AddWithValue("#FindId", FindId);
sqlCmd.Connection.Open();
sda.Fill(dt);
}
}
}
if (dt.Rows.Count == 1)
return Convert.ToInt32(dt.Rows[0]["ID"]);
else if (dt.Rows.Count > 1)
throw new Exception("Multiple records were found with supplied ID; ID = " + studentId.ToString());
}
return 0;
}
To set up your stored procedure, on your database run this:
CREATE procedure [dbo].[SEL_StoredProcedure]
#FindId int = null
as
SELECT * FROM main_list where ID = #FindId
Just remove the index identifier from the code:
e.g.
txtId.Text = dt.Rows["ID"].ToString();

c# getting NullReferenceException when retrieving data from a cell in DataGridView

This is my code:
private void CostList_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'lSEStockDataSet.CostPrice' table. You can move, or remove it, as needed.
this.costPriceTableAdapter.Fill(this.lSEStockDataSet.CostPrice);
con = new System.Data.SqlClient.SqlConnection();
con.ConnectionString = "Data Source=tcp:SHEN-PC,49172\\SQLEXPRESS;Initial Catalog=LSEStock;Integrated Security=True";
con.Open();
DataGridView datagridview1 = new DataGridView();
String retrieveData = "SELECT CostID, SupplierName, CostPrice FROM CostPrice WHERE PartsID ='" + textBox1.Text + "'";
SqlCommand cmd = new SqlCommand(retrieveData, con);
int count = cmd.ExecuteNonQuery();
SqlDataReader dr = cmd.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(dr);
dataGridView1.DataSource = dt;
con.Close();
}
private void button1_Click(object sender, EventArgs e)
{
if (dataGridView1.Rows.Count > 0)
{
int nRowIndex = dataGridView1.Rows.Count-1;
if (dataGridView1.Rows[nRowIndex].Cells[2].Value != null)
{
textBox2.Text = Convert.ToString(dataGridView1.Rows[nRowIndex].Cells[2].Value);
}
else
{
MessageBox.Show("NULL");
}
}
}
It shows NULL when i clikc the button, what is the problem here? I have 3 columns there, i want to get the data of the 3rd column of the last row, but it shows NULL but there is data in the specified cell. Anyone knows how to solve this problem?
Instead of subtracting one from the row count, try subtracting two. Subtracting one is giving you the zero-based index of the "add" row, which indeed has a null value in the last column.
int nRowIndex = dataGridView1.Rows.Count-2;
By subtracting 2 from the count, you will get the zero-based index of the last row with actual data in it. I think this is what you are looking for.
As an aside, you will likely want to parameterize your SQL query, something like this:
String retrieveData = "SELECT CostID, SupplierName, CostPrice FROM CostPrice WHERE PartsID = #inPartsID";
SqlCommand cmd = new SqlCommand(retrieveData, con);
cmd.Parameters.Add(new SqlParameter("#inPartsID", textBox1.Text));
This will make your query more reliable (what happens if there is a single quote character in textBox1) and your data more secure (evil-doers can use SQL injection to cause harm to your database or get data out of it that they shouldn't).

Check if Column Value Exists in Dataset using C# and ASP

I've tried various solutions I have found and either I don't know how to implement them properly or they simply won't work. I have a method that allows someone to search a table for a specific order number, then the rest of the row will display in a gridview. However, if an order number is entered that doesn't exist in the table then I can get server error/exception. How can I make it so that before the search goes through or while the search goes through, if an order number that does't exist in the database is searched for then I can create the error instead?
I am using an ms access database, C#, and ASP.
Here is some of the code I am working with:
the method for searching the order table:
public static dsOrder SearchOrder(string database, string orderNum)
{
dsOrder DS;
OleDbConnection sqlConn;
OleDbDataAdapter sqlDA;
sqlConn = new OleDbConnection("PROVIDER=Microsoft.ACE.OLEDB.12.0;" + "Data Source=" + database);
DS = new dsOrder();
sqlDA = new OleDbDataAdapter("select * from [Order] where order_num='" + orderNum + "'" , sqlConn);
sqlDA.Fill(DS.Order);
return DS;
}
And using that method:
protected void btnSearch_Click(object sender, EventArgs e)
{
Session["OrderNum"] = txtSearch.Text;
Session["ddl"] = ddlSearch.Text;
if (Session["ddl"].ToString() == "Order")
{
dsOrder dataSet2;
dataSet2 = Operations.SearchOrder(Server.MapPath("wsc_database.accdb"), Session["OrderNum"].ToString());
grdSearch.DataSource = dataSet2.Tables["Order"];
grdSearch.DataBind();
}
Do I need to do a try/catch?
A huge thanks in advance to who is able to help me!
You can simply do a check to see whether DataSet is empty
if (dataSet2 == null || dataSet2.Tables.Count == 0 || dataSet2.Tables["Order"] == null || dataSet2.Tables["Order"].Rows.Count == 0)
{
//display error to user
}
else
{
// your code to populate grid
}
If you don't want to show error then just put this check before populating GridView
if (dataSet2.Tables != null && dataSet2.Tables["Order"] != null)
{
// your code to populate grid
}
I use a different approach when filling data grids and always use parameters as follows:
public static DataTable GetGridDatasource(string database, string ordnum) {
using (OleDbConnection con = new OleDbConnection("PROVIDER=Microsoft.ACE.OLEDB.12.0;Data Source=" + database))
{
con.Open();
OleDbCommand cmd = con.CreateCommand();
cmd.CommandText = "select * from [Order] where order_num=[OrderNumber]";
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("OrderNumber", ordnum);
OleDbDataAdapter da = new OleDbDataAdapter(cmd);
DataTable dt = new DataTable();
da.Fill(dt);
return dt;
}
}
protected void btnSearch_Click(object sender, EventArgs e)
{
Session["OrderNum"] = txtSearch.Text;
Session["ddl"] = ddlSearch.Text;
if (Session["ddl"].ToString() == "Order")
{
grdSearch.DataSource = GetGridDatasource(Server.MapPath("wsc_database.accdb"), Session["OrderNum"].ToString());
}
}

Categories