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());
............
}
Related
I want to read all records from "product" table and create objects from each records.
it only gets one records from the database, any ideas might help ?
public IReadOnlyList<Product> Search(string name)
{
var result = new List<Product>();
using (var conn = new SqlConnection(connectionString))
{
if (name == null)
{
var command = new SqlCommand("SELECT * FROM Product ", conn);
conn.Open();
using var reader = command.ExecuteReader();
{
while (reader.Read())
{
var prod = new Product((int)reader["ID"], (string)reader["Name"],
(double)reader["Price"], (int)reader["Stock"], (int)reader["VATID"],
(string)reader["Description"]);
result.Add(prod);
reader.NextResult();
}
reader.Close();
conn.Close();
return result;
};
}
}
If you have several result sets, you should loop over them, i.e. you should put one more outer loop, e.g.
using var reader = command.ExecuteReader();
do {
while (reader.Read()) {
var prod = new Product(
Convert.ToInt32(reader["ID"]),
Convert.ToString(reader["Name"]),
Convert.ToDouble(reader["Price"]), // decimal will be better for money
Convert.ToInt32(reader["Stock"]),
Convert.ToInt32(reader["VATID"]),
Convert.ToString(reader["Description"])
);
result.Add(prod);
}
}
while (reader.NextResult());
Note outer do .. while loop since we always have at least one result set.
You use NextResult which advances the reader to the next result set. This makes sense if you have multiple sql queries and you'd use it after the while-loop. Here it's just unnecessary and wrong.
You are already advancing the reader to the next record with Read.
If I get rid of it, this error occur : Unable to cast object of type
'System.DBNull' to type 'System.String.
You can use IsDBNull:
int nameIndex = reader.GetOrdinal("Name");
string name = reader.IsDBNull(nameIndex) ? null : reader.GetString(nameIndex);
int descIndex = reader.GetOrdinal("Description");
string description = reader.IsDBNull(descIndex) ? null : reader.GetString(descIndex);
var prod = new Product((int)reader["ID"],
name,
(double)reader["Price"],
(int)reader["Stock"],
(int)reader["VATID"],
description);
Use it for every nullable column, for the numeric columns you could use nullable types like int?.
You have an error in your code:
Remove the line reader.NextResult();
NextResult is used for moving to next result set not next record.
Definitely remove the NextResult(). That does NOT move between individual records in the same query. Read() does this for you already. Rather, NextResult() allows you to include multiple queries in the same CommandText and run them all in one trip to the database.
Try this:
public IEnumerable<Product> Search(string name)
{
using (var conn = new SqlConnection(connectionString))
using (var command = new SqlCommand("SELECT * FROM Product ", conn))
{
if (!string.IsNullOrEmpty(name) )
{
command.CommandText += " WHERE Name LIKE #Name + '%'";
command.Parameters.Add("#Name", SqlDbType.NVarChar, 50).Value = name;
}
conn.Open();
using var reader = command.ExecuteReader();
{
while (reader.Read())
{
var prod = new Product((int)reader["ID"], reader["Name"].ToString(),
(double)reader["Price"], (int)reader["Stock"], (int)reader["VATID"],
reader["Description"].ToString());
yield return prod;
}
}
}
}
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();
Due to some reason I need to read entity objects directly from database using ADO.Net.
I've found below snippet from Microsoft documentation. I want to know are there any methods to read whole row into an Onject ('contact' in this sample) using EntityDataReader instead of mapping every single field to every property? I mean instead of reading Contact.Id and Contact.Name and other fields one by one, are there any methods which read one row into one object or not?
using (EntityConnection conn =
new EntityConnection("name=AdventureWorksEntities"))
{
conn.Open();
string esqlQuery = #"SELECT VALUE contacts FROM
AdventureWorksEntities.Contacts AS contacts
WHERE contacts.ContactID == #id";
// Create an EntityCommand.
using (EntityCommand cmd = conn.CreateCommand())
{
cmd.CommandText = esqlQuery;
EntityParameter param = new EntityParameter();
param.ParameterName = "id";
param.Value = 3;
cmd.Parameters.Add(param);
// Execute the command.
using (EntityDataReader rdr =
cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
// The result returned by this query contains
// Address complex Types.
while (rdr.Read())
{
// Display CustomerID
Console.WriteLine("Contact ID: {0}",
rdr["ContactID"]);
// Display Address information.
DbDataRecord nestedRecord =
rdr["EmailPhoneComplexProperty"] as DbDataRecord;
Console.WriteLine("Email and Phone Info:");
for (int i = 0; i < nestedRecord.FieldCount; i++)
{
Console.WriteLine(" " + nestedRecord.GetName(i) +
": " + nestedRecord.GetValue(i));
}
}
}
}
conn.Close();
}
Your cleanest option is to use execute your query using EntityFramework as suggested by #herosuper
In your example, you'd need to do something like this:
EntityContext ctx = new EntityContext();
var contacts= ctx.Contacts
.SqlQuery("SELECT * FROM AdventureWorksEntities.Contacts AS contacts"
+ "WHERE contacts.ContactID =#id", new SqlParameter("#id", 3)).ToList();
From here, you would be able to:
var myvariable = contacts[0].ContactID;//zero is index of list. you can use foreach loop.
var mysecondvariable = contacts[0].EmailPhoneComplexProperty;
Alternatively, you might skip the whole SQL string by by doing this:
EntityContext ctx = new EntityContext();
var contact= ctx.Contacts.Where(a=> a.ContactID ==3).ToList();
I'm assuming the query returns more than one record, otherwise you would just use FirstOrDefault() instead of Where()
I am trying to populate a group of labels in a C# windows form with some values that are in a certain attribute (PlayerName) in a database that I have in access.
Currently the only code I have is:
OleDbConnection connection = new OleDbConnection(CONNECTION STRING HERE);
OleDbCommand command = new OleDbCommand();
command.Connection = connection;
command.CommandText = "SELECT PlayerName FROM [TotalPlayerName] WHERE Team = 1 AND SportID = " + Form1.IDNumber;
I need a list or array that holds these values so I can use them to populate the labels, but I am unaware of how to do this.
You need to call ExecuteReader to obtain a data reader and then loop through the rows of the result set like this:
List<string> result = new List<string>();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
result.Add(reader.GetString(0));
}
}
Before you do this, don't forget to open the connection like this:
connection.Open();
There are a couple of things here..... for sake of best practice well its more standard practice... as I like to say!
Use USING as this cleans up after connection.. see here for great examples in a "using" block is a SqlConnection closed on return or exception?
using (OdbcDataReader DbReader = DbCommand.ExecuteReader())
{
int fCount = DbReader.FieldCount;
while (DbReader.Read())
{
Label1 = DbReader.GetString(0);
Label2 = DbReader.GetString(1);
Label3 = DbReader.GetString(2);
Label4 = DbReader.GetString(3);
for (int i = 0; i < fCount; i++)
{
String col = DbReader.GetString(i);
Console.Write(col + ":");
}
Console.WriteLine();
}
}
NB your SQL only return 1 field /String at the moment
while reading the data fill the list like
List<string> players = new List<string>();
OleDbDataReader rdr = command.ExecuteReader();
While(rdr.Read())
{
players.Add(rdr["PlayerName"].ToString());
}
You need to create a OleDbReader object to read the response from the query. You will also need to create a List to store the data Something like this:
List<string> playerNameList = new List<string>();
using (OleDbReader r = command.ExecuteReader())
{
while(reader.Read())
{
playerNameList.Add(reader.GetString(0));
}
}
One option might be using OleDbDataAdapter to fill a DataTable those values that returns your query;
var dt = new DataTable();
using(var da = new OleDbDataAdapter(command))
{
da.Fill(dt);
}
And since your query return one column, you can use AsEnumerable to that datatable to get them as a string like;
List<string> list = dt.AsEnumerable()
.Select(r => r.Field<string>("PlayerName"))
.ToList();
You can read: Queries in LINQ to DataSet
By the way, you should always use parameterized queries. This kind of string concatenations are open for SQL Injection attacks.
Also use using statement to dispose your connection and command automatically as I did for OleDbDataAdapter in my example.
hello i build a webservice in visual studio 2010. i get some id's which are saved in a string looks like this:
string room_ids="5,11,99,42";
they are separated by comma. i created a foreach loop to split the ids and from the comma and use them in my sql query until the ids are finished. but it doesn't work. i get an error it says:
Error converting data type nvarchar to numeric
here is my code, thanks in advance for your help!
internal static List<RAUM> Raum(string RAUMKLASSE_ID, string STADT_ID, string GEBAEUDE_ID, string REGION_ID)
{
List<RAUM> strasseObject = new List<RAUM>();
string[] allegebaude = GEBAEUDE_ID.Split(new char[] { ',' });
foreach (string gebaudeid in allegebaude)
{
Trace.WriteLine("SIND JETZT DRINNE");
Trace.WriteLine(gebaudeid);
using (SqlConnection con = new SqlConnection(#"Data Source=Localhost\SQLEXPRESS;Initial Catalog=BOOK-IT-V2;Integrated Security=true;"))
using (SqlCommand cmd = new SqlCommand(#"SELECT r.BEZEICHNUNG AS BEZEICHNUNG, r.ID AS ID FROM RAUM r WHERE RAUMKLASSE_ID = ISNULL(#Raumklasse_ID, RAUMKLASSE_ID) AND STADT_ID = ISNULL(#Stadt_ID, STADT_ID) AND GEBAEUDE_ID = ISNULL(#gebaudeid,GEBAEUDE_ID ) AND REGION_ID = ISNULL(#Region_ID, REGION_ID)", con))
{
con.Open();
if (!StringExtensions.IsNullOrWhiteSpace(RAUMKLASSE_ID))
cmd.Parameters.AddWithValue("#Raumklasse_ID", RAUMKLASSE_ID);
else
cmd.Parameters.AddWithValue("#Raumklasse_ID", DBNull.Value);
if (!StringExtensions.IsNullOrWhiteSpace(STADT_ID))
cmd.Parameters.AddWithValue("#Stadt_ID", STADT_ID);
else
cmd.Parameters.AddWithValue("#Stadt_ID", DBNull.Value);
if (!StringExtensions.IsNullOrWhiteSpace(GEBAEUDE_ID))
cmd.Parameters.AddWithValue("#gebaudeid", GEBAEUDE_ID);
else
cmd.Parameters.AddWithValue("#gebaudeid", DBNull.Value);
if (!StringExtensions.IsNullOrWhiteSpace(REGION_ID))
cmd.Parameters.AddWithValue("#Region_ID", REGION_ID);
else
cmd.Parameters.AddWithValue("#Region_ID", DBNull.Value);
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
if (rdr["BEZEICHNUNG"] != DBNull.Value && rdr["ID"] != DBNull.Value)
{
strasseObject.Add(new RAUM()
{
RaumName = rdr["BEZEICHNUNG"].ToString(),
RaumID = rdr["ID"].ToString()
});
}
}
}
}
}
return strasseObject;
}
If you already have the IDs in a comma-separated string (called IDstring) then you can just do something like this:
sqlQuery = "SELECT Columns FROM table WHERE ID IN (" + IDstring + ")";
In your specific case, don't split the original string (GEBAEUDE_ID) but use it as it is:
// Don't use a foreach loop any more
string gebaudeIdSection = " AND GEBAEUDE_ID IN (" + GEBAEUDE_ID + ") ";
if (string.IsNullOrEmpty(GEBAUDE_ID)) { gebaudeIdSection = ""; } // if there are no ids, let's remove that part of the query completely.
using (SqlConnection con = new SqlConnection(#"Data Source=Localhost\SQLEXPRESS;Initial Catalog=BOOK-IT-V2;Integrated Security=true;"))
using (SqlCommand cmd = new SqlCommand(#"SELECT r.BEZEICHNUNG AS BEZEICHNUNG, r.ID AS ID FROM RAUM r WHERE RAUMKLASSE_ID = ISNULL(#Raumklasse_ID, RAUMKLASSE_ID) AND STADT_ID = ISNULL(#Stadt_ID, STADT_ID)" + gebaudeIdSection + " AND REGION_ID = ISNULL(#Region_ID, REGION_ID)", con))
{ // The rest of the code is the same as before...
First of all I think you have to correct:
cmd.Parameters.AddWithValue("#gebaudeid", GEBAEUDE_ID);
with:
cmd.Parameters.AddWithValue("#gebaudeid", gebaudeid);
Then, try to convert the ids into integers ( for example, using Convert.ToInt32(gebaudeid) ) and not to pass them as strings in AddWithValue method.
try this:
if (!StringExtensions.IsNullOrWhiteSpace(gebaudeid))
cmd.Parameters.AddWithValue("#gebaudeid", Convert.ToInt32(gebaudeid));
you should also pass the right parameter to your AddWithValue statement. You are iterating over the list of your ID's, that list being allegebaude. So you have to pass the gebaudeid parameter to your statement, instead of what you're doing now.
You can't implicitly treat a comma separated list of values; instead you'll need to create a table values function to split the list of values into a table structure and then use this structure as you would normally.
Have a look at this question: INSERT INTO TABLE from comma separated varchar-list for a sample function.