Prevent Duplicate Entries into ListView/Access DB [closed] - c#

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
I want to scan in serial numbers into a listview which stores the data into an access db. When the user clicks Submit, the program should either store the data or display a message that indicates the serial number was a duplicate. Here is my code:
Here is how I connect to the database:
namespace Serial_Number_Checker
{
public partial class Form1:Form
{
static string conString = ""
OleDbConnection con = new OleDbConnection(conString);
OleDbCommand cmd;
OleDbDataAdapter adapter;
DataTable dt = new DataTable();
Here is how I create columns for the listview:
public Form1()
{
InitializeComponent();
listView1.SelectedIndexChanged += new EventHandler(listView1_SelectedIndexChanged); // Adding columns to listView1
// list view properities
listView1.View = View.Details;
listView1.FullRowSelect = true;
// Add Columns
listView1.Columns.Add("Employee #", 150);
listView1.Columns.Add("Serial Number", 150);
listView1.Columns.Add("Date/Time", 150);
}
Here is the Add to listView portion:
// Add To ListView1
private void populate(String employeeid, String sn, String timestamp)
{
// Row
String[] row = { employeeid, sn, timestamp };
ListViewItem item = new ListViewItem(row);
listView1.Items.Add(item);
}
Here is the retrieve function:
// Retrieve Check In
private void retrieve()
{
listView1.Items.Clear();
//Sql statement
String sql = "Select * FROM SN_Incoming";
cmd = new OleDbCommand(sql, con);
//Open connection, retrieve, and fill listview1
try
{
con.Open();
adapter = new OleDbDataAdapter(cmd);
adapter.Fill(dt);
//Loop thru dt
foreach (DataRow row in dt.Rows)
{
populate(row[0].ToString(), row[1].ToString(), row[2].ToString());
}
con.Close();
//Clear datatable
dt.Rows.Clear();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
con.Close();
}
}
This is the submit button portion:
private void button1_Click(object sender, EventArgs e)
{
foreach (ListViewItem itemSelected in listView1.SelectedItems)
{
listView1.Items.Remove(itemSelected);
}
if (textBox1.Text == "")
{
MessageBox.Show("Please Enter A Serial Number!", "Input");
}
else
{
add(textBox1.Text);
}
textBox1.Text = "";
textBox1.Focus(); // Set Focus
textBox1.SelectionStart = textBox1.Text.Length == 0 ? 0 : textBox1.Text.Length - 1; // set text selection to end
textBox1.SelectionLength = 0; // Set text Selection Length
retrieve();
}
This should be most of the code. Anything helps.

From my understanding you want to prevent adding duplicate records to your ListView control. I will cover preventing duplicates in your ListView and in your Access Database.
ListView
I would handle this by assigning the serial number to the Tag property of each ListViewItem prior to adding it to the control. Then to ensure no duplicates are added, I would then use that Tag as a reference point for the new serial number to be added. For example:
// Make sure you include the using statement at the top of your code file.
using System.Linq;
private void populate(string employeeid, string, sn, string timestamp) {
ListViewItem lvi = listView1.Items.Cast<ListViewItem>()
.SingleOrDefault(s => s.Tag == sn);
if (lvi == null) {
lvi = new ListViewItem(employeeid);
lvi.SubItems.Add(sn);
lvi.SubItems.Add(timestamp);
lvi.Tag = sn;
listView1.Items.Add(lvi);
} else
MessageBox.Show("Serial number supplied already exists.");
}
The solution above utilizes LINQ which stands for Language Integrated Query. It was introduced in .NET 3.5. The method utilized essentially attempts to find a ListViewItem that contains the Tag property with a value of the supplied serial number.
The call to Cast allows you to utilize the SingleOrDefault method available to IEnumerable since the Items property of ListView does not derive from IEnumerable, but instead ListViewItemCollection.
You can take this down to a lower level of .NET by implementing a foreach loop instead of utilizing LINQ. For example:
private void populate(string employeeid, string, sn, string timestamp) {
foreach (ListViewItem lvi in listView1.Items) {
if (lvi.Tag == sn) {
MessageBox.Show("Serial number supplied already exists.");
return;
}
}
// We know the serial number doesn't exist, so just add the new item.
ListViewItem lvi = new ListViewItem(employeeid);
lvi.SubItems.Add(sn);
lvi.SubItems.Add(timestamp);
lvi.Tag = sn;
listView1.Items.Add(lvi);
}
Database
Since you're using Access for your database, you can follow this guide on preventing duplicate values in a column (in your case it will prevent duplicate serial numbers). Essentially, you just create a unique index for the field you wish to have unique. This is done by setting the field's Indexed property to Yes.
Set a field's Indexed property to Yes (No duplicates)
In the Navigation Pane, right-click the table that contains the field, and then click Design View.
Select the field that you want to make sure has unique values.
In the Field Properties pane, on the General tab, set the Indexed property to Yes (No duplicates).
Useful Points
LINQ expressions are broken down by the compiler to their basic implementations. For example, SingleOrDefault loops through a collection of objects, attempts to find a single object that meets the specified criteria, and then returns the result. If no object was found, it returns null. This is done with a foreach loop.
The foreach loop is actually faster than using LINQ in most cases. On a small scale this wouldn't even be noticed; however, increase the number of ListViewItem objects that you have to iterate through into the 300,000+ range and you have a substantial difference.
Also, it is more effective to add all of your ListViewItem objects to a generic collection, then finally to the ListView via AddRange. If I remember correctly, this is because Add and AddRange cause the ListView to re-sort, and as point 2 above stated, the more items you have, the worse this impacts performance. I've included an example of this kind of implementation below:
Add Range Implementation
List<ListViewItem> items = new List<ListViewItem>();
for (int i = 0; i < 50000; i++) {
ListViewItem lvi = new ListViewItem($"Some kind of text {i}.");
lvi.Tag = i;
items.Add(lvi);
}
listView1.Items.AddRange(items.ToArray());
Refactor Of Your Code
I took the liberty of refactoring your code to teach you more simplistic/efficient ways to accomplish your goal. You may want to take a look at the documentation for: using statement, string.IsNullOrWhiteSpace(), string.Empty. The one thing I will clue you in on with the using statement is that it will automatically close any open connections when the final portion of the block is executed, this is due to the disposal of the object:
When the lifetime of an IDisposable object is limited to a single method, you should declare and instantiate it in the using statement. The using statement calls the Dispose method on the object in the correct way, and (when you use it as shown earlier) it also causes the object itself to go out of scope as soon as Dispose is called. Within the using block, the object is read-only and cannot be modified or reassigned.
private void Click_Submit(object sender, EventArgs e) {
foreach (ListViewItem lvi in listView1.SelectedItems)
listView1.Items.Remove(lvi);
if (string.IsNullOrWhiteSpace(textBox1.Text))
MessageBox.Show("Please enter a serial number!", "Input");
else
InserSerialNumber(textBox1.Text);
textBox1.Text = string.Empty;
textBox1.Focus();
textBox1.SelectionStart = textBox1.Text.Length == 0 ? 0 : textBox1.Text.Length - 1;
textBox1.SelectionLength = 0;
LoadSerialNumbers();
}
private void InsertSerialNumber(string sn) {
using (OleDbCommand odc = new OleDbCommand("INSER INTO SN_Incoming(SN) VALUES(#SN)", con)) {
odc.Parameters.AddWIthValue("#SN", sn);
try {
con.Open();
odc.ExecuteNonQuery();
} catch (Exception e) { MessageBox.Show(e.Message); } finally { con.Close(); }
}
}
private void LoadSerialNumbers() {
listView1.Items.Clear();
DataTable dt = new DataTable();
using (OleDbCommand odc = new OleDbCommand("SELECT * FROM SN_Incoming", con)) {
try {
con.Open();
using (OleDbDataAdapter oda = new OleDbDataAdapter(odc))
ida.Fill(dt);
} catch (Exception e) { MessageBox.Show(e.Message); } finally { con.Close(); }
}
List<ListViewItem> items = new List<ListViewItem>();
foreach (DataRow row in dt.Rows) {
ListViewItem lvi = items.SingleOrDefault(s => s.Tag == row[1].ToString());
if (lvi != null)
continue;
lvi = new ListViewItem(new string[] { row[0].ToString(), row[1].ToString(), row[2].ToString() });
lvi.Tag = row[1].ToString();
items.Add(lvi);
}
listView1.Items.AddRange(items.ToArray());
}

Related

2 same value on gridview

hello i am currently having search button on my form and i want to search is between 2 date and i want to have a alert message if i have 2 same record on other column this is my code for now
SqlConnection sqlCon = new SqlConnection(ConnectionString);
if (sqlCon.State == ConnectionState.Closed)
sqlCon.Open();
SqlDataAdapter sqlData = new SqlDataAdapter("DateFilter", sqlCon);
sqlData.SelectCommand.CommandType = CommandType.StoredProcedure;
sqlData.SelectCommand.Parameters.AddWithValue("#Date", TxtFromDate.Text);
sqlData.SelectCommand.Parameters.AddWithValue("#Date2", TxtToDate.Text);
DataTable dtbl = new DataTable();
sqlData.Fill(dtbl);
sqlCon.Close();
Gridview1.DataSource = dtbl;
Gridview1.DataBind();
Gridview1.UseAccessibleHeader = true;
Gridview1.HeaderRow.TableSection = TableRowSection.TableHeader;
It is really hard decrypting your question. It seems you have an existing SQL result in your DataGridView and then want to check via a button if there are multiple entries with the same "CreatedDate" value.
(1) You should use defined objects in order to be able to work with object items instead of DataTable row/column indexes which is a hassle.
Therefore you need to define your object, map your DataTable rows as objects items in a collection (here: a simple List<>) and then set this collection to your DataGridView.DataSource, e.g.:
// define single object
public class FinishedGood {
public int ID { get; set; }
public DateTime CreatedDate { get; set; }
...
}
// define object collection
public class FinishedGoods : List<FinishedGood> { }
// execute your existing SQL query and fill the DataTable
...
// convert DataTable to FinishedGoods
FinishedGoods finishedGoods = new FinishedGoods();
foreach (DataRow row in dtbl.Rows) {
finishedGoods.Add(new FinishedGood() {
ID = Convert.ToInt32(row["ID"]),
CreatedDate = DateTime.Parse(row["CreatedDate"].ToString());,
...
});
}
// set the collection as datasource
gv.DataSource = finishedGoods;
(2) Now, you can check the DataSource for duplicates via Linq:
using System.Linq;
private HashSet<FinishedGood> CheckForDuplicateCreatedDates() {
HashSet<FinishedGood> result = new HashSet<FinishedGood>();
// collect all createdDates
HashSet<DateTime> createdDates = new HashSet<DateTime>();
foreach (FinishedGood finishedGood in gv.DataSource){
if (!createdDates.Contains(finishedGood.createdDate)) {
createdDates.Add(finishedGood.createdDate);
}
}
// loop through all createdDates
foreach (DateTime createdDate in createdDates) {
// check if there are more than 2 entries with the createdDate in your DataSource
if (gv.DataSource.Count(x => x.CreatedDate == createdDate) > 2) {
// add those entries to your duplicate result list
foreach (FinishedGood finishedGood in gv.DataSource.Where(x => x.CreatedDate == createdDate)) {
result.Add(finishedGood);
}
}
}
return result;
}
(3) To show the alert popup, you can use MessageBox.Show().
(4) To highlight the corresponding rows in the DataGridview, you can use the result of (2) to find the corresponding row indexes and then adjust their DefaultCellStyle.
The button click would look something like this:
private void buttonCheck_Click(object sender, EventArgs e) {
// get duplicates
HashSet<FinishedGood> duplicates = CheckForDuplicateCreatedDates();
if (duplicates.Count() > 0) {
// show alert popup
MessageBox.Show("Duplicates found");
// highlight the corresponding rows
foreach (DataGridViewRow row in gv.Rows) {
if (duplicates.Contains((row.DataBoundItem as FinishedGood))) {
row.DefaultCellStyle.BackColor = Color.DarkRed;
row.DefaultCellStyle.ForeColor = Color.White;
}
}
}
}

For the first TextBox txtName, it is showing a dropdown list but for the other TextBox i am not getting any dropdownlist

Below code is of .net windows form app showing 2 TextBoxes with drop down suggestion when user types something in it. the code for first TextBox txtName is working but the other one is not....i need hellp on this.
private void txtName_TextChanged(object sender, EventArgs e)
{
connection = new SqlConnection();
connection.ConnectionString = "Data Source=DESKTOP-H1PE2LT;Initial Catalog=HealthandWellness;Integrated Security=True";
connection.Open();
cmd = new SqlCommand("SELECT [Medicine Name] FROM Details", connection);
reader = cmd.ExecuteReader();
while (reader.Read())
{
AutoCompleteStringCollection collection = new AutoCompleteStringCollection();
collection.Add(reader["Medicine Name"].ToString());
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
connection = new SqlConnection();
connection.ConnectionString = "Data Source=DESKTOP-H1PE2LT;Initial Catalog=HealthandWellness;Integrated Security=True";
connection.Open();
cmd = new SqlCommand("SELECT [Medicine Name] FROM Details", connection);
reader = cmd.ExecuteReader();
while (reader.Read())
{
AutoCompleteStringCollection collection1 = new AutoCompleteStringCollection();
collection1.Add(reader["Medicine Name"].ToString());
}
}
To say that one of these is working and the other is not leads me to believe that you have set up something in the gui properties because I see nowhere where you set this stringcollection to your autocompletecustomsource. That being said, let's use 2 new textboxes and set up a demo that you can test on your own machine.
For the building of the AutoCompleteStringCollection, I'm going to just use a list but you would use your query. I will try to denote where you would use those methods.
Since you're using the same data for both textboxes, we only need to create the collection once. We'll do this in the form load.
private void Form1_Load(object sender, EventArgs e)
{
//this would be replaced with your query results from the database
string[] input = { "able", "alpha", "baker", "bravo", "charlie", "charcoal", "dog", "delta", "eagle", "foxtrot" };
List<string> pretendThisCameFromAQuery = new List<string>(input);
//define an AutoCompleteStringCollection. Notice it is outside of the loop as you want to add to this with each iteration of the read() or in this case the foreach loop i'm using.
AutoCompleteStringCollection collection = new AutoCompleteStringCollection();
//here is where your While reader.read() would go
foreach (string item in pretendThisCameFromAQuery)
{
collection.Add(item);
//your code here would be something like
//collection.Add(reader["Medicine Name"].ToString());
}
//so now we have a collection. Who wants to use it.
//for txtName1, we'll set it up to just suggest.
txtName1.AutoCompleteCustomSource = collection;
txtName1.AutoCompleteMode = AutoCompleteMode.Suggest;
txtName1.AutoCompleteSource = AutoCompleteSource.CustomSource;
//for textbox2 we'll use suggestappend.
//notice no new collection needed.
textBox2.AutoCompleteCustomSource = collection;
textBox2.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
textBox2.AutoCompleteSource = AutoCompleteSource.CustomSource;
}
Notice that this code is not in any of the textbox events. Especially the TextChanged event as that would run with every change to the textbox and we don't need to do that as we only need to fill the collection and assign it to the textbox(s) that we want to use the collection with before we need to use the dropdown.
Again, if you run this demo, I strongly suggest you add two new textboxes named as I have them named because I really feel you've done something outside of writing code to get the first one to work.

Change text of button when button is clicked c#

First time messing around with c# and I am trying to change the value of a button when the user clicks it.
The database has four names in it, when the code executes it only shows the name that is in the last row. I would like it to start with the first name, and then each time the button is pressed it should show another name until it has gone through all the names in the DB.
dataReader = command.ExecuteReader();
while (dataReader.Read())
{
bText = dataReader.GetValue(1).ToString();
button1.Text = bText;
}
The while loop does not work the way you think it does. What it does is looping over every result and set the button text. It does not pause anywhere (because there is nothing that tells the loop it should). So it simply iterates over every element as fast as it can. This explains why you only see the last result.
What you can do is to store a counter somewhere that you increment every time the button is pressed and only use the n-th element. Either by skipping the first few entries or depending on the database you use, you could only fetch a specific result entry (in MySQL this would be LIMIT and OFFSET in the select query)
For your specific problem (only 4 entries) I would fetch all entries at the start of the application and store them in an array. Then use the entry at the current index (=your counter) to set the text of the button.
You can try this. I've table named Colors having two columns: ID and Name.
public partial class FrmColor : Form
{
SqlConnection con = new SqlConnection(#"<YOUR CONNECTION STRING HERE>");
int pointer = 1;
int rows = 0;
public FrmColor()
{
InitializeComponent();
}
private void btnColor_Click(object sender, EventArgs e)
{
SqlCommand cmd = new SqlCommand("Select Name from Colors Where ID = #ID", con);
cmd.Parameters.AddWithValue("#ID", pointer);
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
rdr.Read();
string colorname = rdr.GetValue(0).ToString();
con.Close();
btnColor.Text = colorname;
if (pointer == rows)
{
pointer = 1;
}
else
{
pointer++;
}
}
private void FrmColor_Load(object sender, EventArgs e)
{
SqlCommand cmd = new SqlCommand("Select count(Name) From Colors", con);
con.Open();
rows = (Int32)cmd.ExecuteScalar();
con.Close();
}
}
Don't forget to add using System.Data.SqlClient;
Hope this helps.

dynamic autocomplete textbox on textchange event in winform with database

how can achieve dynamic auto complete textbox in c# winform?
for example ,in my application have a textbox named txtbox_mobileno.
i wanna bind mobile number(from database table named customer) with txtbox_mobileno.
the table customer have more than 100,000 records .client want suggestion list while typing mobile number in application .
so i tried to implement following code on textchanged event
if (txtbox_mobileno.TextLength >3)
{
cmd.CommandText = "SELECT TOP 20 MobileNo FROM Customer WHERE MobileNo LIKE'" + txtbox_mobileno.Text+"%'";
SqlDataReader dr;
dr = RunSqlReturnDR(cmd);//return value
if (dr.HasRows)
{
while (dr.Read())
{
C.Add(dr[0].ToString()); //AutoCompleteStringCollection C = new AutoCompleteStringCollection();
}
}
dr.Close();
txtbox_mobileno.AutoCompleteMode = AutoCompleteMode.Suggest;
txtbox_mobileno.AutoCompleteSource = AutoCompleteSource.CustomSource;
txtbox_mobileno.AutoCompleteCustomSource = C;
}
i want to show the suggestion list only if text box have more than 3 letters
when tried to bind on load event, it took around 5 minutes to bind data with textbox suggestion list ,
Sql server query execution time was only 3 seconds.
is there any fastest way to overcome this issue?
I am not sure if you are able to grab all mobile numbers at the beginning of your app or at least when your data entry form loads up, but if so, it may be easier to load all of the numbers into buckets with the first three characters being the key for the list of numbers. Even if you had 1,000,000 numbers, you are only talking around 30 MB of data (assuming I did my math correctly), so you shouldn't have any issues in that regard. You could do something like this to build your buckets. As an aside, I also have no idea how many numbers you get that have the same first three digits, so I may be building very large autocomplete lists in this code which would be another problem. Your code only pulled back the top 20, you could modify this code to only keep the first 20 or something like that.
var buckets = new Dictionary<string, List<string>>();
cmd.CommandText = "SELECT MobileNo FROM Customer";
SqlDataReader dr = RunSqlReturnDR(cmd);
if (dr.HasRows)
{
while (dr.Read())
{
var number = dr[0].ToString();
var key = number.Substring(0, 3);
List<string> numbers = null;
if(!buckets.TryGetValue(key, out numbers))
{
numbers = new List<string>();
}
numbers.Add(number);
}
}
dr.Close();
Then in your event handler you just need to do something like this:
if (txtbox_mobileno.Text.Length == 3)
{
List<string> numbers;
if (_buckets.TryGetValue(txtbox_mobileno.Text, out numbers)
{
var ac = new AutoCompleteStringCollection();
ac.AddRange(numbers.ToArray());
txtbox_mobileno.AutoCompleteMode = AutoCompleteMode.Suggest;
txtbox_mobileno.AutoCompleteSource = AutoCompleteSource.CustomSource;
txtbox_mobileno.AutoCompleteCustomSource = ac;
}
}
Other ways you could potentially enhance this would be to add an order by in the query where you get the numbers, then you should be able to walk the values and just create a new list when you hit a change in the first three digits. That would make it easier to build a dictionary of arrays rather than a dictionary of lists, which would then help in your autocomplete box. I also haven't written a lot of DB code in a while, so I just kept the code you posted for getting the data out of the database. It is possible that there may be better ways to read the data from the database.
Try use a timer to fire query, and a Thread to execute, see:
private void textBox1_TextChanged(object sender, EventArgs e)
{
timer1.Enabled = false;
timer1.Enabled = true;
}
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Enabled = false;
string filter = txtbox_mobileno.Text;
Thread t = new Thread(() => {
cmd.CommandText = "SELECT TOP 20 MobileNo FROM Customer WHERE MobileNo LIKE '" + filter + "%'";
SqlDataReader dr;
dr = RunSqlReturnDR(cmd);//return value
if (dr.HasRows)
{
while (dr.Read())
{
C.Add(dr[0].ToString()); //AutoCompleteStringCollection C = new AutoCompleteStringCollection();
}
}
dr.Close();
txtbox_mobileno.Invoke((MethodInvoker)delegate {
txtbox_mobileno.AutoCompleteMode = AutoCompleteMode.Suggest;
txtbox_mobileno.AutoCompleteSource = AutoCompleteSource.CustomSource;
txtbox_mobileno.AutoCompleteCustomSource = C;
});
});
t.Start();
}

How to set first index as blank in combobox

I have a combobox that is bound with a datasource. In this combobox I have to add a blank field at index 0.
I have written following code for getting records.
public List<TBASubType> GetSubType(int typ)
{
using (var tr = session.BeginTransaction())
{
try
{
List<TBASubType> lstSubTypes = (from sbt in session.Query<TBASubType>()
where sbt.FType == typ
select sbt).ToList();
tr.Commit();
return lstSubTypes;
}
catch (Exception ex)
{
CusException cex = new CusException(ex);
cex.Write();
return null;
}
}
}
After this it bind with combobox with data binding source as below code.
M3.CM.BAL.CM CMobj = new M3.CM.BAL.CM(wSession.CreateSession());
lstSubTypes = CMobj.GetSubType(type);
this.tBASubTypeBindingSource.DataSource = lstSubTypes;
If you just want to select nothing initially, you can use
comboBox1.SelectedIndex=-1;
Thus you can't modify Items when you are are bound to DataSource, then only option to add blank row is modifying your data source. Create some empty object and add it to data source. E.g. if you have list of some Person entities bound to combobox:
var people = Builder<Person>.CreateListOfSize(10).Build().ToList();
people.Insert(0, new Person { Name = "" });
comboBox1.DisplayMember = "Name";
comboBox1.DataSource = people;
You can define static property Empty in your class:
public static readonly Person Empty = new Person { Name = "" };
And use it to insert default blank item:
people.Insert(0, Person.Empty);
This also will allow to check if selected item is default one:
private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
Person person = (Person)comboBox.SelectedItem;
if (person == Person.Empty)
MessageBox.Show("Default item selected!");
}
cboCustomers.Items.Add(""); // Just add a blank item first
// Then load the records from the database
try
{
OleDbConnection con = new OleDbConnection(strDBConnection);
OleDbCommand cmd = new OleDbCommand();
con.Open();
cmd.Connection = con;
cmd.CommandText = "SELECT * FROM Customers";
OleDbDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
cboCustomers.Items.Add(dr["Name"]);
}
con.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
After creating my combo boxes, I added these lines to the end of the Load() method:
private void xyz_Load(object sender, EventArgs e)
{
this.xyzTableAdapter.Fill(this.DataSet.xyz);
this.comboBoxXYZ.SelectedIndex = -1;
}
replace xyz with the names you've given to your controls

Categories