I am using a SqlDataReader to fetch data from a stored procedure. Even though the records are being fetched, the while (reader.Read()) gets executed only once, and so in my list only one row is added.
List<Student> tablelist = new List<Student>();
using (SqlConnection con = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("SP_ReadPromotedStudents"))
{
cmd.Connection = con;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#Name", SqlDbType.VarChar).Value = Data[0];
cmd.Parameters.Add("#Email", SqlDbType.VarChar).Value = Data[1];
cmd.Parameters.Add("#Class", SqlDbType.VarChar).Value = Data[2];
con.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.HasRows)
{
while (reader.Read())
{
tablelist.Add(new Student
{
Name = (string)(reader[0]),
Email = (string)(reader[1]),
Class = (string)(reader[2]),
});
reader.NextResult();
}
}
}
}
}
return tablelist;
My Student class:
public class Student
{
public string Name { get; set; }
public string Email { get; set; }
public string Class { get; set; }
}
I have about 46 records being fetched. But in the list only one record gets added. What is the mistake here?
You need to move your call to NextResult outside the reader.Read() loop. Otherwise after the first read the code encounters the NextResult call and tries to load a second sets of data returned by the stored procedure.
Also the loop over HasRows is an infinite loop. If the property reader.HasRows is true it will be true also when you finish to read the rows.
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
tablelist.Add(new Student
{
Name = (string)(reader[0]),
Email = (string)(reader[1]),
Class = (string)(reader[2]),
});
}
// This should be called only if your stored procedure returns
// two or more sets of data otherwise you can remove everything
reader.NextResult();
// If there is another set of data then you can read it with a
// second while loop with
while(reader.Read())
{
.....
}
}
The ideal scenario would to have a new sql statement to get just what you want instead the get a list and need just the first access. Imagine if you have a table with millions of records, would you need to execute a query to get all and read just the first one? No, you execute a query to get the you need.
The NextResult method from DataReader moves the pointer to the next result if you have it on the result. Remove it.
After you chanfge the sql statement to get what you need, you are looping the result set. You could read just the first line (changing the while to if):
if (reader.Read())
{
tablelist.Add(new Student
{
Name = (string)(reader[0]),
Email = (string)(reader[1]),
Class = (string)(reader[2]),
});
}
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;
}
}
}
}
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've got this code block:
using (SqlConnection con2 = new SqlConnection(str2))
{
using (SqlCommand cmd2 = new SqlCommand(#"SELECT * FROM VW_MOS_DPL_AccountValidation WHERE CUST_NUM = #CNum", con2))
{
con2.Open();
cmd2.Parameters.AddWithValue("#CNum", TBAccountNum.Text);
using (SqlDataReader DT2 = cmd2.ExecuteReader())
{
// If the SQL returns any records, process the info
if (DT2.HasRows)
{
// If there's a BusinessID (aka Business Type), fill it in
string BizID = (DT2["Business_ID"].ToString());
if (!string.IsNullOrEmpty(BizID))
{
DDLBustype.SelectedValue = BizID;
}
}
}
con2.Close();
}
}
When it gets to the line
string BizID = (DT2["Business_ID"].ToString());
it throws an error:
Invalid attempt to read when no data is present.
Why would it get past if (DT2.HasRows) if there was no data?
You need to call
if(DT2.Read())
....
before proceding to read data from a DataReader.
The HasRows tells you only that the SqlDataReader contains data, but the SqlDataReader loads one record at time from the connection. Thus every tentative to extract the data from the SqlDataReader should be preceded by a call to Read to position the SqlDataReader on the first record returned through the connection.
And, because the Read method returns true if the call has been able to read a record, you could replace the call to HasRows with something like this
using (SqlDataReader DT2 = cmd2.ExecuteReader())
{
// If the SQL returns any records, process the info
while(DT2.Read())
{
// If there's a BusinessID (aka Business Type), fill it in
string BizID = (DT2["Business_ID"].ToString());
if (!string.IsNullOrEmpty(BizID))
{
DDLBustype.SelectedValue = BizID;
}
}
}
By the way, if it is possible to have a NULL for BusinessID then you need a different test to avoid exception problems
int bizColIndex = DT2.GetOrdinal("Business_ID");
string BizID = (DT2.IsDBNull(bizColIndex) ? string.Empty : DT2.GetString(bizColIndex));
if (!string.IsNullOrEmpty(BizID))
{
DDLBustype.SelectedValue = BizID;
}
At the moment, I have this which works fine:
using (connection = new SqlConnection("connection string here"))
{
using (command = new SqlCommand(#"select * from tbl1", connection))
{
connection.Open();
using (reader = command.ExecuteReader())
{
while (reader.Read())
{
int ColIndex1 = reader.GetOrdinal("col_1");
int ColIndex2 = reader.GetOrdinal("col_2");
Console.Write(reader.GetString(ColIndex1);
Console.Write(" - ");
Console.Write(reader.GetString(ColIndex2);
Console.Write(Environment.NewLine);
}
}
}
}
I have another query which I run separately, but that second query needs the first query, which means I end up running the first query twice. To avoid that, if I changed the command line to:
using (command = new SqlCommand(#"select * from tbl1; select * from tbl2", connection))
How do I get each query into a separate list? I understand how to get a single query into a list, i.e:
public class Data
{
public int ColumnIndex1 { get; set; }
public int ColumnIndex2 { get; set; }
}
List<Data> list = new List<Data>();
list.Add(new Data(ColIndex1, ColIndex2));
The first query is used to create directories on the hard drive. The second query then uses the first query and then adds files to the created directories.
using (connection = new SqlConnection("connection string here"))
{
using (command = new SqlCommand(#"select * from tbl1", connection))
{
connection.Open();
using (reader = command.ExecuteReader())
{
while (reader.Read())
{
// read first grid
}
if(reader.NextResult())
{
while (reader.Read())
{
// read second grid
}
}
}
}
}
However, I strongly suggest using helper tools, for example, via "dapper":
List<FirstType> first;
List<FirstType> second;
using(var multi = connection.QueryMultiple(sql, args))
{
first = multi.Read<FirstType>().ToList();
second = multi.Read<SecondType>().ToList();
}
I think you need to investigate the NextResult method on the IDataReader interface. This allows you to move through multiple result sets.
http://msdn.microsoft.com/en-us/library/system.data.idatareader.nextresult(v=vs.110).aspx
I'm having a problem outputting to a WebGrid because my list gets overwritten, so by the end, I have the final line of data written for every line on the grid. I have to use a while loop because the data is continually being added to, and we're looking at alot of data, so I'm trying not to write to another list.
public class ChemData
{
string strSQLconnection = "Server=Server;Database=data;Uid=Username;Pwd=Password";
public int productId { get; set; }
public string productName { get; set; }
public List<ProdData> ProdList = new List<ProdData>();
public List<ProdData> ProdDataPull()
{
ProdData Analysis = new ProdData();
SqlDataReader reader = null;
SqlConnection conn = new SqlConnection(strSQLconnection);
SqlCommand query = new SqlCommand("Select * from producttable");
conn.Open();
query.Connection = new SqlConnection(strSQLconnection);
query.Connection.Open();
reader = query.ExecuteReader();
while (reader.Read())
{
if (!reader.IsDBNull(0)) Analysis.productId = reader.GetInt32(0);
if (!reader.IsDBNull(1)) Analysis.productName = reader.GetString(1);
ProdList.Add(Analysis);
}
return ChemList;
}
}
This is because Analysis is created only once. Adding it to the list each time adds a reference to the same object. Moving the creation to inside the while loop should fix this
while (reader.Read())
{
ProdData Analysis = new ProdData();
if (!reader.IsDBNull(0)) Analysis.productId = reader.GetInt32(0);
if (!reader.IsDBNull(1)) Analysis.productName = reader.GetString(1);
ProdList.Add(Analysis);
}
This creates a new ProdData object on each iteration of the loop and assigns it to Analysis, then updates its contents and adds that reference to the list.
With the creation of Analysis outside the loop Analysis continues to point to the same ProdData object that gets added over and over to the list while its values are overwritten each time.
See : Reference Types vs. Value Types