C# Modify List item while in a foreach loop - c#

as the title say's I am attempting to get to edit a list item inside of a foreach loop. I know it is not possible, so I am asking for solutions to work around this.
As this is taking data directly from a MySQL server, it need's to be done inside of this loop and with the correct indexes.
I attempted to use a second list and change the data outside of the foreach loop, but that did not account for the index positions of the items.
foreach (string friend in friendsOnline)
{
query = "SELECT `username` FROM `accounts` WHERE email = '" + friend + "'";
cmd = new MySqlCommand(query, conn);
rdr = cmd.ExecuteReader();
while (rdr.Read())
{
for (int i = 0; i<friendsOnline.Count; i++)
{
if (friendsOnline[i].Contains(friend))
{
friendsOnline[i] = rdr[0].ToString();
}
}
}
}

As I can see, you have emails in the friendsOnline:
string[] friendsOnline = new[] {
"Me#Shire.com",
"Dark.Lord#Mordor.gov",
};
and you want to change these emails into names:
string[] friendsOnline = new[] {
"Frodo Baggins",
"Sauron the Black",
};
First of all, I suggest executing query just once: SQL is too slow to be run in a loop; instead you can build a dictionary which eMail corresponds to which indexes:
using System.Linq;
...
// eMail - indexes correspondence
Dictionary<string, int[]> dict = friendsOnline
.Select((name, index) => (name : name, index : index))
.GroupBy(pair => pair.name, pair => pair.index, StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key, group => group.ToArray());
// We take all users (no WHERE), but once
// We read both username and email
string sql =
#"SELECT `username`,
`email`
FROM `accounts`";
//DONE: using - we should Dispose IDisposable Command and Reader
using cmd = new MySqlCommand(sql, conn);
using rdr = cmd.ExecuteReader();
while (rdr.Read()) {
string name = Convert.ToString(rdr[0]);
string mail = Convert.ToString(rdr[1]);
// If we have correspondent indexes, we change friendsOnline
if (dict.TryGetValue(mail, out var indexes))
foreach (var index in indexes)
friendsOnline[index] = name;
}

Related

'The variable name '#' has already been declared. Variable names must be unique within a query batch or stored procedure.'

string subject= "INSERT INTO Subjects (ThesisNo, [Subject]) VALUES (2, #subject)";
SqlCommand commandSubject = new SqlCommand(subject,con);
string temp;
foreach (var control in checkedListBox.CheckedItems)
{
temp = control.ToString();
commandSubject.Parameters.AddWithValue("#subject", temp);
}
con.Open();
commandSubject.ExecuteNonQuery();
con.Close();
I want to add items that are checked in the Checkedlistbox to #subject.
But I can only add one item. How can I add all checked items to #subject?
You're adding parameters inside the loop. Add outside, and set the value inside and execute every time:
using(SqlConnection con = ...) {
string subject= "INSERT INTO Subjects(ThesisNo,[Subject]) VALUES (2, #subject)";
con.Open();
SqlCommand commandSubject = new SqlCommand(subject,con);
commandSubject.Parameters.Add("#subject", SqlDbType.VarChar);
foreach (var control in checkedListBox.CheckedItems)
{
commandSubject.Parameters["#subject"].Value = control.ToString();
commandSubject.ExecuteNonQuery();
}
}
Or if you're wanting to execute once you need to modify the SQL, and add as you go, so that you end up with an SQL like e.g. INSERT INTO .. VALUES (2,#p0), (2,#p1), (2,#p2) and a parameters collection that is 3 long (#p0 to #p2) with 3 different data values:
using(SqlConnection con = ...) {
string subject= "INSERT INTO Subjects(ThesisNo,[Subject])VALUES";
SqlCommand commandSubject = new SqlCommand(subject, con);
int p=0;
foreach (var control in checkedListBox.CheckedItems)
{
commandSubject.CommandText += $"(2,#subject{p}),"
commandSubject.Parameters.Add($"#subject{p}", SqlDbType.VarChar).Value = control.ToString();
p++;
}
commandSubject.CommandText = commandSubject.CommandText.TrimEnd(','); //remove trailing comma from concat
con.Open();
commandSubject.ExecuteNonQuery();
}
If you have thousands of these string concats to do, use a StringBuilder. For what you might reasonably want a user to tick in a UI (20 or fewer?) string concat will be fine
The best method depends on whether the number of items is large and variable.
If that is the case either a Table-Valued Parameter or using SqlBulkCopy is the most performant option.
Here is how you would do SqlBulkCopy:
var table = new DataTable{
Columns = {
{"ThesisNo", typeof(int)},
{ "Subject", typeof(string)
}
};
foreach (var control in checkedListBox.CheckedItems)
DataTable.Rows.Add(2, control.ToString());
using(var bulk = new SqlBulkCopy(con, SqlBulkCopyOptions.CheckConstraints | SqlBulkCopyOptions.FireTriggers)
{
DestinationTableName = "Subjects"
})
bulk.WriteToServer(table);
The other method, which, at a push, you can get away with on smaller sets, is to add a numbered parameter for each item. The maximum is 2100. Do NOT do this if range of possible rowcounts is large, otherwise you will kill your query plan store:
using(SqlCommand comm = new SqlCommand("",con))
{
var n = 0;
foreach (var control in checkedListBox.CheckedItems)
comm.Parameters.Add("#subject" + (++n), SqlDbType.Varchar, *sizeofcolumnhere*).Value = control.ToString();
comm.CommandText = "INSERT INTO Subjects(ThesisNo,[Subject])\r\nVALUES\r\n" +
string.Join("\r\n",
Enumerable.Range(1, n).Select(i => $"(2 ,#subject{i})");
comm.ExecuteNonQuery();
}
Add the line 'SqlCommand commandSubject = new SqlCommand(subject,con)' to the foreach loop:
string subject= "INSERT INTO Subjects (ThesisNo, [Subject]) VALUES
(2, #subject)";
string temp;
foreach (var control in checkedListBox.CheckedItems)
{
SqlCommand commandSubject = new SqlCommand(subject,con);
temp = control.ToString();
commandSubject.Parameters.AddWithValue("#subject", temp);
}
con.Open();
commandSubject.ExecuteNonQuery();
con.Close();

Find longest string from DataReader column

Is It possible to create a Linq for retrieving longest string values of DataReader columns ? Column data should be converted to string and then return longest string.
What I know so far is how to get same thing with DataTable which is Enumerable (as I asked here), example:
string maxString = dt
.AsEnumerable()
.Select(row => row[mycolumn].ToString())
.OrderByDescending(st => st.Length)
.FirstOrDefault();
I've tried combining upper solution for DataReader like this:
var enumerable = reader
.Cast<IDataRecord>();
string max_string = enumerable
.Select(record => record.GetName(col).ToString())
.Aggregate((s, a) => a.Length > s.Length ? a : s);
Unfortunally this doesn't work, I get
Sequence contains no elements in at
System.Linq.Enumerable.Aggregate
error. Thanks for help in advance :)
EDIT: I'm looking for a solution without loading data into Datatable etc., just directly from DataReader object. I'm trying to avoid "Out of memory exception", because data is large.
Latest attempt, suggested by Power Mouse (It returns correct value, but from column 1 only):
for (int col = 0; col < reader.FieldCount; col++)
{
string col_name = reader.GetName(col).ToString();
var enumerable = reader.Cast<IDataRecord>();
string max_string = enumerable.Where(x => enumerable.Max(y => y[col_name].ToString()
.Length) == x[col_name].ToString().Length)
.FirstOrDefault()?[col_name].ToString();
Console.WriteLine("max string of column is : " + max_string);
}
so according to your original request: you need to find the longest text in specific column when you utilizing a DataReader on the fly.
please review example
string storedString = String.Empty;
SqlConnection connection = new SqlConnection(this.Connection.ConnectionString);
using (connection)
{
string SQLcommand = "select * FROM (VALUES(1, 'xxx' ), (2, 'really long string xxxxxx'), (3, 'short string'), (4, 'another string')) t (id, fName)";
SqlCommand command = new SqlCommand(SQLcommand, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
storedString = reader.Cast<IDataRecord>()
.Where(w=> w.GetOrdinal("fName").ToString().Length == reader.Cast<IDataRecord>()
.Max(m => m.GetOrdinal("fName")
.ToString().Length))
.Select(s=> s.GetString(1))
.FirstOrDefault();
}
}
Console.WriteLine($"The longest string: {storedString}. charcount= {storedString.Length}");
the result would be :
The longest string: really long string xxxxxx. charcount= 25
as you explained that you need to check multiple columns:
string storedNameString = String.Empty;
string storedName2String = String.Empty;
SqlConnection connection = new SqlConnection(this.Connection.ConnectionString);
using (connection)
{
string SQLcommand = "select * FROM (VALUES(1, 'xxx', 'dddddd' ), (2, 'really long string xxxxxx','dfghdt'), (3, 'short string', 'anothercolumn long string'), (4, 'another string', 'test')) t (id, fName, fName2)";
SqlCommand command = new SqlCommand(SQLcommand, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
string fName = reader.GetString(reader.GetOrdinal("fName")).ToString();
if(fName.Length >= storedNameString.Length)
storedNameString = fName;
string fName2 = reader.GetString(reader.GetOrdinal("fName2")).ToString();
if (fName2.Length >= storedName2String.Length)
storedName2String = fName2;
}
}
Console.WriteLine($"The longest string: {storedNameString}. charcount= {storedNameString.Length}");
Console.WriteLine($"The longest string: {storedName2String}. charcount= {storedName2String.Length}");
I solved my problem, unfortunally without LINQ. Problem is that with DataReader you cannot just simply loop through rows & columns as you can with DataTable once stored in memory, but you must perfom somekind of same logic while reader.Read() method is running.
So, best thing I could came up with is to store column indexes and their string values into Dictionary while .Read() method is running.
Doing that, you must be careful about string blank spaces & null values. Here is my solution, which runs good for me:
Dictionary<int, string> col_values = new Dictionary<int, string>();
using (OracleDataReader reader = cmd.ExecuteReader())
{
for (int i = 0; i < reader.FieldCount; i++)
{
//First add indexes to Dictionary
// I add column names here - didn't test for string.Empty !!
col_values.Add(i, string.Empty);
}
//Then read row by row and modify Dictionary - If text is larger than string.Empty
//Dictionary must be .ToArray(), or else you'll have an error for modifying collection
while (reader.Read())
{
foreach (var item in col_values.ToArray())
{
string rdr_text = reader[item.Key].ToString().Trim()??string.Empty;
if (item.Value.Length<rdr_text.Length)
{
col_values[item.Key] = rdr_text;
}
}
}
foreach (var item in col_values)
{
//...And here we have all longest strings stored, for each column...Job done
}
}
For my purpuses this Iteration reads around 2.3 mio rows with 12 columns in 4 minutes. It's not fast, but at least It works. If anyone has better/faster idea please provide answer.

C# MySQL adding multiple of the same parameter with a loop

Update: as the original question was essentially answered, I've marked this as complete.
I have a C# project in which I'd like to query a database. The SQL query will SELECT from a table, and in the WHERE clause I want to filter the results from a pre-defined list of values specified in C#.
List<string> productNames = new List<string >() { "A", "B", "C" };
foreach (name in productNames)
{
string query = #"
SELECT *
FROM products
WHERE name IN (#name);";
// Execute query
MySqlCommand cmd = new MySqlCommand(query, dbConn);
cmd.Parameters.AddWithValue("name", name);
MySqlDataReader row = cmd.ExecuteReader();
while (row.Read())
{
// Process result
// ...
}
}
However I'm getting an error:
There is already an open DataReader associated with this Connection
which must be closed first
Is it not possible then to use a for loop to add parameters this way to a SELECT statement?
You need to dispose your object to not get the exception. However you don't need to iterate over values and run a query for every value in the list. Try the following code. It makes a parameter for every value and adds it to command to use in "IN (...)" clause.
Also "using" keywords handles disposing objects.
List<string> productsIds = new List<string>() { "23", "46", "76", "88" };
string query = #"
SELECT *
FROM products
WHERE id IN ({0});";
// Execute query
using (MySqlCommand cmd = new MySqlCommand(query, dbConn))
{
int index = 0;
string sqlWhere = string.Empty;
foreach (string id in productsIds)
{
string parameterName = "#productId" + index++;
sqlWhere += string.IsNullOrWhiteSpace(sqlWhere) ? parameterName : ", " + parameterName;
cmd.Parameters.AddWithValue(parameterName, id);
}
query = string.Format(query, sqlWhere);
cmd.CommandText = query;
using (MySqlDataReader row = cmd.ExecuteReader())
{
while (row.Read())
{
// Process result
// ...
}
}
}
You are doing few things wrong. Let me point out them:
Everything is fine in the first iteration of the loop, when you are in second iteration the First Reader associated with the command remains opened and that result in current error.
You are using Where IN(..) you you can specify the values within IN as comma separated so there is no need to iterate through parameters.
You can use String.Join method to construct this values with a comma separator.
Now take a look into the following code:
List<int> productsIds = new List<int>() { 23, 46, 76, 88 };
string idInParameter = String.Join(",", productsIds);
// Create id as comma separated string
string query = "SELECT * FROM products WHERE id IN (#productId);";
MySqlCommand cmd = new MySqlCommand(query, dbConn);
cmd.Parameters.AddWithValue("#productId", idInParameter);
MySqlDataReader row = cmd.ExecuteReader();
while (row.Read())
{
// Process result
// ...
}
Please note if the id field in the table is not integers then you have to modify the construction of idInParameter as like the following:
idInParameter = String.Join(",", productsIds.Select(x => "'" + x.ToString() + "'").ToArray());
Pass the comma separated productIds string instead of looping. Read more about IN here.
string productIdsCommaSeparated = string.Join(",", productsIds.Select(n => n.ToString()).ToArray())
string query = #"
SELECT *
FROM products
WHERE id IN (#productId);";
// Execute query
MySqlCommand cmd = new MySqlCommand(query, dbConn);
cmd.Parameters.AddWithValue("productId", productIdsCommaSeparated );
MySqlDataReader row = cmd.ExecuteReader();
while (row.Read())
{
// Process result
// ...
}
The error you are getting is because you do not close the MySqlDataReader. You must close it to get rid of error before you call ExecuteReader in next iteration but this is not proper way in this case.
From what I tried and tested seems best solution (especially for text column types) is to create a list of individual parameters and add it as a range the to the query and parameters.
e.g:
List<MySqlParameter> listParams = new List<MySqlParameter>();
for (int i = 0; i < listOfValues.Length; i++)
{
listParams.Add(new MySqlParameter(string.Format("#values{0}", i),
MySqlDbType.VarChar) { Value = listOfValues[i] });
}
string sqlCommand = string.Format("SELECT data FROM table WHERE column IN ({0})",
string.Join(",", listParams.Select(x => x.ParameterName)));
......
using (MySqlCommand command = new MySqlCommand(sqlCommand, connection)
{
............
command.Parameters.AddRange(listParams.ToArray());
............
}

How to retrieve data from database to CheckedListBox and set the items as checked?

I am new in WPF. I am trying to load the values from database to fill in CheckedListBox. Based on a condition, some items must be set to checked while loading in checkedlistbox.
How to do this? I have tried the code below, items are loaded in CheckedListBox, but are not checked.
Below is values loaded to checked listbox
public void fillcheck()
{
con = new SqlConnection(connectionstring);
con.Open();
string comboquery = "SELECT [Machine] FROM Department Where active='True'";
SqlCommand cmd = new SqlCommand(comboquery, con);
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
string fil1 = rdr.GetString(0);
Checkedlistbox1.Items.Add(fil1);
}
rdr.Close();
}
int departmentID=60//for just refer
Object[] jobs = CheckedlistBox1.Items.Cast<Object>().ToArray();
foreach (Object obj in jobs)
{
string query = "SELECT [Machine] FROM Department Where ID='" + departmentID+ "'";
SqlCommand cmd = new SqlCommand(query, con);
SqlDataReader rdr = cmd.ExecuteReader();
while(rdr.Read())
{
string mac = rdr.GetString(0);//Here i get two values(XRAY,CT)but finally shown CT only be checked,so how to do both checked
if (mac == obj.ToString())
{
int indexx = CheckedlistBox1.Items.IndexOf(mac);
if (indexx >= 0)
{
CheckedlistBox1.SetItemChecked(indexx, true);
}
}
}
rdr.Close();
}
You need to transfer your SqlDataReader rdr content to a DataTable. That will help you get a DataTable object containing multiple rows like you have mentioned.
Now for the next step, you can apply a foreach on that DataTable object to iterate over all its rows like this :
foreach(DataRow dr in dt.Rows)
{
if(yourCondition)
{
//set isChecked = true for the checkbox.
}
}
UPDATE :
Try modifying your while loop like this :
while (rdr.Read())
{
string mac = rdr.GetString(0);
ListItem li = new ListItem();
li.Value = "yourBindedValue";// some value from database column
li.Text = "yourBindedText";// use mac if its text.
int index = Checkedlistbox1.Items.IndexOf(li);
if (index >= 0)
{
Checkedlistbox1.SetItemChecked(index, true);
}
}
I have tested this and it works. You just have to pass the Text and Value of the CheckBoxListItem that you are trying to find in the li object and you can get the index if it exists. Make sure you pass both the attributes.
You should have used code-
foreach (int indexChecked in chlstBox.Items)
instead of
foreach (int indexChecked in chlstBox.CheckedIndices)
At start you have 0 selected items and thats why your outer for loop is not working..
EDIT-
Basic Logic is also incorrect.
You should loop through dataset, find the string in checkboxlist and then check it. So, outer foreach loop is not required. Also, make sure that you are using correct checkboxlist variable. In for loop you are using chlstBox
and while searching you are using Checkedlistbox1 ....

How to find particular value arraylist

I have one array list:
public ArrayList GetExpenseTypes()
{
ArrayList expArry = new ArrayList();
using (SqlConnection conn = new SqlConnection(connStr))
{
string sql = "SELECT ID, TITLE";
sql += " FROM EXPENSE_TYPE";
SqlCommand cmd = new SqlCommand(sql, conn);
SqlDataReader reader;
try
{
conn.Open();
reader = cmd.ExecuteReader();
while (reader.Read())
{
expArry.Add(new ListItem(reader["TITLE"].ToString(), reader["ID"].ToString()));
}
reader.Close();
}
catch (Exception ex)
{
}
finally
{
conn.Close();
}
}
return expArry;
}
My ArrayList like this
1 name1
2 name2
3 name3
4 name4
5 name6
if my value is 4 i need to display name4
How i achieve that?
Instead of ArrayList you might want to use Dictionary<string,string> like this.
public IDictionary<string,string> GetExpenseTypes()
{
Dictionary<string,string> expArry = new Dictionary<string,string>();
// Your sql code
while (reader.Read())
{
expArry.Add(reader["TITLE"].ToString(), reader["ID"].ToString());
}
// The rest of your code
}
Then you can get the value like this
var values = GetExpenseTypes();
string valueYouWant = values["4"];
If on the other hand your problem is that when you use the ListItems in a web control you are seeing the wrong values, then you need to swap the parameters when you create the ListItem because the first parameter is the text that is displayed and the second is the value. Like this.
expArry.Add(new ListItem(reader["ID"].ToString(), reader["TITLE"].ToString()));
In which case you should consider using a List<ListItem> instead of ArrayList
you could use BinarySearch method if you are searching for value types; in your case this does not seem possible.
I think you may need to use a loop assuming that you can not use Linq (because of the framework employed);
int index = -1;
for(int i=0; i<expArray.Count;i++)
{
ListItem temp = (expArray[i] as ListItem);
if(temp != null && temp.Value == "some value")
{
index = i;
break;
}
}
I'd recommend first changing ArrayList to List<ListItem>. If you can't do that for some reason you could use (assuming each item is unique):
var x = expArry.Cast<ListItem>().SingleOrDefault(exp => exp.Value == value);
if (x != null)
//item was found
If you know the item will always be there just use Single if it will only be there once.
Don't use ArrayList, do something like this using a generic dictionary
public IDictionary<int, string> GetExpenseTypes()
{
var result = new Dictionary<int, string>();
using (var conn = new SqlConnection(connStr))
{
var getExpenses = new SqlCommand(
"SELECT ID, TITLE FROM EXPENSE_TYPE",
conn);
conn.Open();
using (var reader = command.ExecureReader())
{
while (reader.Read())
{
result.Add(reader.GetInt32(0), reader.GetString(1));
}
}
}
return result;
}
Then you could look up your string like this
var type4 = GetExpenseTypes()[4];
alternatively, don't get the whole list to find one value.
public string GetExpenseType(int id)
{
using (var conn = new SqlConnection(connStr))
{
var getExpenseType = new SqlCommand(
"SELECT TITLE FROM EXPENSE_TYPE WHERE ID = #p0",
conn);
getExpenseType.Parameters.Add(id);
conn.Open();
return (string)getExpenseType.ExecuteScalar();
}
}
If, for some bad reason, you want to stick with your ArrayList returning function, you can find your item like this.
var expenseTypes = GetExpenseTypes().OfType<ListItem>().ToDictionary(
li => int.Parse(li.Text),
li => li.Value);
var type4 = expenseTypes[4];
or, if you want to do this once.
var type4 = GetExpenseTypes().OfType<ListItem>()
.Single(li => li.Text == "4").Value;

Categories