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;
}
}
}
}
Related
I have this code which returns the correct data from one table. But I have related data in other tables using INNER JOIN. So my question is how to code to return this in the result?
IList<Schedule> GetCurrentValues()
{
var result = new List<Schedule>();
using (var sqlConnection = new SqlConnection(_configuration["DefaultConnection"]))
{
sqlConnection.Open();
using (var command = sqlConnection.CreateCommand())
{
command.CommandText = "SELECT Schedules.AppointmentHeading, Schedules.AppointmentDateStart, Schedules.AppointmentDateEnd, Bookers.Email, Rooms.Id AS Expr3, Rooms.Name " +
"FROM Schedules " +
"INNER JOIN Rooms ON Schedules.RoomId = Rooms.Id " +
"INNER JOIN Bookers ON Schedules.BookerId = Bookers.Id";
command.CommandType = CommandType.Text;
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
// How should I code so I also get Bookers.Email and Room.Name?
}
}
}
}
}
return result;
}
Since you need a mixed data from different tables, it is recommended to either create new Model which will consist of the properties you actually care like
AppointmentHeading, AppointmentDateStart, AppointmentDateEnd, BookingEmail, RoomName
or just add BookingEmail & RoomName to your existing Schedule Model.
using (var sqlConnection = new SqlConnection(_configuration["DefaultConnection"]))
{
sqlConnection.Open();
using (var command = sqlConnection.CreateCommand())
{
command.CommandText = "SELECT Schedules.AppointmentHeading, Schedules.AppointmentDateStart, Schedules.AppointmentDateEnd, Bookers.Email as BookingEmail, Rooms.Id AS Expr3, Rooms.Name as RoomName" +
"FROM Schedules " +
"INNER JOIN Rooms ON Schedules.RoomId = Rooms.Id " +
"INNER JOIN Bookers ON Schedules.BookerId = Bookers.Id";
command.CommandType = CommandType.Text;
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
result.AppointmentHeading = Convert.ToString(reader["AppointmentHeading"]);
result.AppointmentStart = Convert.ToDateTime(reader["AppointmentDateStart"]);
result.AppointmentEnd = Convert.ToDateTime(reader["AppointmentDateEnd"]);
result.BookingEmail = COnvert.ToString(reader["BookingEmail"]);
result.RoomName = Convert.ToString(reader["RoomName"]);
}
}
}
}
}
Just reference the column names in the SqlDataReader - it returns a "flat" set of all the columns selected, and you can access these via the column name (or the column alias AS .... if one is given). Then store those values as needed, e.g. in a separate class or whatever works for you:
....
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
// read the individual values and store them as needed
string appointmentHeading = reader["AppointmentHeading"];
DateTime appointmentStart = Convert.ToDateTime(reader["AppointmentDateStart"]);
DateTime appointmentEnd = Convert.ToDateTime(reader["AppointmentDateEnd"]);
string email = reader["Email"];
string roomId = reader["Expr3"];
string roomName = reader["Name"];
// possibly create a custom class here to hold these values
// and store the multiple possible rows returned into a list of those classes
// - or do whatever you need to do with these values here.....
}
}
Create a new class containing all the columns of select statement.
I have a very silly problem. I am doing a select, and I want that when the value comes null, return an empty string. When there is value in sql query, the query occurs all ok, but if there is nothing in the query, I have to give a sqlCommand.CommandTimeout greater than 300, and yet sometimes gives timeout. Have a solution for this?
public string TesteMetodo(string codPess)
{
var vp = new Classe.validaPessoa();
string _connection = vp.conString();
string query = String.Format("SELECT COUNT(*) FROM teste cliente WHERE cod_pess = {0}", codPess);
try
{
using (var conn = new SqlConnection(_connection))
{
conn.Open();
using (var cmd = new SqlCommand(query, conn))
{
SqlDataReader dr = cmd.ExecuteReader();
if(dr.HasRows)
return "";
return codPess;
}
}
}
You should probably validate in the UI and pass an integer.
You can combine the usings to a single block. A bit easier to read with fewer indents.
Always use parameters to make the query easier to write and avoid Sql Injection. I had to guess at the SqlDbType so, check your database for the actual type.
Don't open the connection until directly before the .Execute. Since you are only retrieving a single value you can use .ExecuteScalar. .ExecuteScalar returns an Object so must be converted to int.
public string TesteMetodo(string codPess)
{
int codPessNum = 0;
if (!Int32.TryParse(codPess, out codPessNum))
return "codPess is not a number";
var vp = new Classe.validaPessoa();
try
{
using (var conn = new SqlConnection(vp.conString))
using (var cmd = new SqlCommand("SELECT COUNT(*) FROM teste cliente WHERE cod_pess = #cod_pess", conn))
{
cmd.Parameters.Add("#cod_pess", SqlDbType.Int).Value = codPessNum;
conn.Open();
int count = (int)cmd.ExecuteScalar();
if (count > 0)
return "";
return codPess;
}
}
catch (Exception ex)
{
return ex.Message;
}
}
Why only the else condition runs? Postalcode column is float, City column is nvarchar. I think the fail is the string may be mistake.
private void txt_st_postalcode_Leave(object sender, EventArgs e)
{
using (var connection = new SqlConnection("Data Source=mypublicip\\SQLEXPRESS2017;Initial Catalog=studentreg; User = myusername; Password=mypassword;"))
{
connection.Open();
using (var command = new SqlCommand("SELECT City FROM Cities WHERE Postcode=#Postcode", connection))
{
command.Parameters.AddWithValue("#Postcode", "10101");
using (var reader = command.ExecuteReader())
{
string txt_st_postalcode = reader.Read() ?
reader[1] as string : ("City");
if (reader.Read())
{
txt_st_city.Text = reader.GetString(reader.GetOrdinal("City"));
}
else
{
MessageBox.Show("Sh*t!");
}
}
}
}
}
Not too sure how these postcodes work (different in the UK), but given that you're using ExecuteReader you appear to be expecting multiple results. As the comments have pointed out, you're currently reading the results twice; however, you should probably have some form of loop; for example:
using (var command = new SqlCommand("SELECT City FROM Cities WHERE Postcode=#Postcode", connection))
{
command.Parameters.AddWithValue("#Postcode", "10101");
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
string txt_st_postalcode = reader[0] as string;
//txt_st_city.Text = reader.GetString(reader.GetOrdinal("City"));
// Depends what you're doing here?
}
}
}
If you only expect a single result, try using ExecuteScalar() which will return a single result; for example:
using (var command = new SqlCommand("SELECT City FROM Cities WHERE Postcode=#Postcode", connection))
{
command.Parameters.AddWithValue("#Postcode", "10101");
txt_st_city.Text = command.ExecuteScalar();
}
Firstly, if you need to read PostCode from the reader, you have to select first. So, change your query.
using (var command = new SqlCommand("SELECT City, PostCode FROM Cities WHERE Postcode=#Postcode", connection))
Secondly, calling read once per row before getting data.
if (reader.Read())
{
txt_st_postalcode .Text = reader.GetString(reader.GetOrdinal("PostCode"));
txt_st_city.Text = reader.GetString(reader.GetOrdinal("City"));
}
else
{
MessageBox.Show("Sh*t!");
}
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.
I created an list named 'PTNList', and everything I needed added to it just fine. Now I am attempting to write code to retrieve each element from that list and run it against an SQL query. I have a feeling I'm not sure exactly how to about this. The CompareNumbers.txt file generates, but nothing is printed to it. Any help is greatly appreciated.
Below is the section of code I believe needs to be worked with.
using (FileStream fs = new FileStream("c:/temp/CompareNumbers.txt", FileMode.Append, FileAccess.Write))
using (StreamWriter sw = new StreamWriter(fs))
foreach (var ptn in PTNList)
{
//create sql for getting the count using "ptn" as the variable thats changing
//call DB with sql
//Get count from query, write it out to a file;
Console.WriteLine("Running Query");
string query2 = #"SELECT COUNT(PRODUCT_TYPE_NO)
AS NumberOfProducts
FROM dbo.PRODUCT
Where PRODUCT_TYPE_NO = " + ptn;
SqlCommand cmd2 = new SqlCommand(query2);
cmd2.Connection = con;
rdr = cmd2.ExecuteReader();
while (rdr.Read())
{
sw.WriteLine(rdr["NumberOfProducts"]);
}
rdr.Close();
}
You haven't used apostrophes around the values. But you should use parameters anyway. You could use one query instead of one for every type. For example with this approach:
string sql = #"SELECT COUNT(PRODUCT_TYPE_NO) AS NumberOfProducts
FROM dbo.PRODUCT
Where PRODUCT_TYPE_NO IN ({0});";
string[] paramNames = PTNList.Select(
(s, i) => "#type" + i.ToString()
).ToArray();
string inClause = string.Join(",", paramNames);
using (SqlCommand cmd = new SqlCommand(string.Format(sql, inClause)))
{
for (int i = 0; i < paramNames.Length; i++)
{
cmd.Parameters.AddWithValue(paramNames[i], PTNList[i]);
}
// con.Open(); // if not already open
int numberOfProducts = (int) cmd.ExecuteScalar();
}
Update: maybe you really just want to loop them and get their count. Then you don't need this complex approach. But you should still use sql-parameters to prevent sql-injection and other issues like missing apostrophes etc.
You'll want to convert the column back to a type, e.g.
sw.WriteLine(rdr["NumberOfProducts"] as string);
Also, note that your query is prone to SqlInjection attacks and should be parameterized, and that SqlCommand is also disposable. You can squeeze a bit more performance by reusing the SqlCommand:
string query2 = #"SELECT COUNT(PRODUCT_TYPE_NO)
AS NumberOfProducts
FROM dbo.PRODUCT
Where PRODUCT_TYPE_NO = #ptn";
using (var cmd2 = new SqlCommand(query2))
{
cmd2.Connection = con;
cmd2.Parameters.Add("#ptn", SqlDbType.Varchar);
foreach (var ptn in PTNList)
{
cmd2.Parameters["#ptn"].Value = ptn;
Console.WriteLine("Running Query");
using var (rdr = cmd2.ExecuteReader())
{
if (rdr.Read())
{
sw.WriteLine(rdr["NumberOfProducts"] as string);
}
}
}
}
Are you sure your query give an result and sw.WriteLine is executed? I would redesign your code like this, becfause if you have an error in your data query, you might get into trouble. I always like to use this (schema):
IDataReader reader = null;
try
{
// create every thing...
}
catch(Exception ex)
{
// catch all exceptions
}
finally()
{
if(reader != null)
{
reader.Close();
}
}
And use the same for your connection, so that you can be sure, it is closed correct.