SqlDataReader closed for unknown reason - c#

C# noob here. I am trying to get some data from a SQL Data Base using the code below, however i get the error: System.InvalidOperationException: 'Invalid attempt to call Read when reader is closed.'
After searching for the answer online, i found nothing, maybe someone can see what is wrong?
The error happens at: while (rdr.Read())
Thank you!
internal List<string> GetInflationByYearData(string Country, int StartYear,
int EndYear)
{
string CS =
ConfigurationManager.ConnectionStrings["Connection"].ConnectionString;
using (SqlConnection con = new SqlConnection(CS))
{
SqlCommand cmd = new SqlCommand("SpCountryData", con)
{
CommandType = System.Data.CommandType.StoredProcedure
};
cmd.Parameters.AddWithValue("#act",
"getInflationByYearData");
cmd.Parameters.AddWithValue("#Country", Country);
cmd.Parameters.AddWithValue("#startYear", StartYear);
cmd.Parameters.AddWithValue("#endYear", EndYear);
var table = new DataTable();
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
{
table.Load(rdr);
};
}
//con.Close();
List<string> labelList = new List<string>();
List<string> valueList = new List<string>();
if (table.Rows.Count > 0)
{
foreach (DataRow row in table.Rows)
{
string label = row["Year"].ToString();
string value = row["Percentage"].ToString();
labelList.Add(label);
valueList.Add(value);
}
}
List<string> list = new List<string>();
StringBuilder sbLabels = new StringBuilder();
foreach (string lbl in labelList)
{
sbLabels.Append(lbl + ",");
}
StringBuilder sbValues = new StringBuilder();
foreach (string val in valueList)
{
sbValues.Append(val + ",");
}
list.Add(sbLabels.ToString().Substring(0, sbLabels.Length -
1));
list.Add(sbValues.ToString().Substring(0, sbValues.Length -
1));
return list;
}
}

Load consumes the reader to completion; you should either have a while (rdr.Read()) loop, or call table.Load(rdr), but: not both. Load basically does that same loop internally.
So: remove the loop - just use Load.
However! If all you want is the data as List<string>, loading it into a DataTable seems like an unnecessary step (DataTable is not lightweight) - it seems like you could do that in the reader, or perhaps use other tools (like "dapper") to remove that.
It isn't clear what data types Year and Percentage are, but as a "dapper" example...
class MyRowThing {
public int Year {get;set;}
public decimal Percentage {get;set;}
}
var rawData = con.Query<MyRowThing>("SpCountryData",
new { // params here
act = "getInflationByYearData", Country,
startYear = StartYear, endYear = EndYear
}, commandType: CommandType.StoredProcedure).AsList();
// now loop over rawData, which is a List<MyRowThing> with the data

Replacing:
var table = new DataTable();
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
{
table.Load(rdr);
};
}
//con.Close();
List<string> labelList = new List<string>();
List<string> valueList = new List<string>();
if (table.Rows.Count > 0)
{
foreach (DataRow row in table.Rows)
{
string label = row["Year"].ToString();
string value = row["Percentage"].ToString();
labelList.Add(label);
valueList.Add(value);
}
}
With:
con.Open();
List<string> labelList = new List<string>();
List<string> valueList = new List<string>();
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
labelList.Add(rdr["Year"].ToString());
valueList.Add(rdr["Percentage"].ToString());
}
Will get rid of the DataTable for a more optimized code.

Related

How to sort a dataset in ASP.NET C#

I am trying to sort a datatable into a DataSet. I want to sort by the Status Column in "DESC". But I am not aware how to go about this. I have tried the suggested solutions online but I seem not to be doing something right. Here is what I have tried, albeit, I have commented out the sorting lines of the code as they do not work for me. How can I sort my table using the Status column in Desc?
[WebMethod(EnableSession = true)]
public List < TaskListClass > getTasks() {
var userId = Session["UserId"].ToString();
List < TaskListClass > objB = new List < TaskListClass > ();
try {
using(var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DBConnString"].ToString())) {
connection.Open();
DataSet Myds = new DataSet();
// Myds.Tables[0].DefaultView.Sort = "Status desc";
SqlDataAdapter sqldr = new SqlDataAdapter();
string ProcName = "getTasks";
SqlCommand cmd = new SqlCommand(ProcName, connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#userId", SqlDbType.VarChar, 900).Value = userId;
sqldr.SelectCommand = cmd;
sqldr.Fill(Myds);
DataTable dt = Myds.Tables[0];
// DataTable dt = Myds.Tables[0].DefaultView.ToTable();
for (int i = 0; i < dt.Rows.Count; i++) {
objB.Add(new TaskListClass() {
Id = Convert.ToString(dt.Rows[i]["Id"]),
Subject = Convert.ToString(dt.Rows[i]["Subject"]),
Customer = Convert.ToString(dt.Rows[i]["Customer"]),
Sender = Convert.ToString(dt.Rows[i]["Sender"]),
Receiver = Convert.ToString(dt.Rows[i]["Receiver"]),
Priority = Convert.ToString(dt.Rows[i]["Priority"]),
StartDate = Convert.ToString(dt.Rows[i]["StartDate"]),
EndDate = Convert.ToString(dt.Rows[i]["EndDate"]),
Status = Convert.ToString(dt.Rows[i]["Status"]),
OnProgress = Convert.ToString(dt.Rows[i]["OnProgress"]),
});
}
}
} catch (Exception e) {
msg = e.ToString();
}
return objB;
}
Ok, a few things.
first up, a dataset is a collection of tables - "many tables" possible.
But you have ONE table, so why use a dataset? I see no need. Just use a single data table for this.
And this will reduce the code.
So, I suggest this, or close to this:
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DBConnString"].ToString()))
{
using (var cmd = new SqlCommand("getTasks", connection))
{
connection.Open();
cmd.CommandType = CommandType.StoredProcedure;
DataTable dt = new DataTable();
cmd.Parameters.Add("#userId", SqlDbType.VarChar).Value = userId;
dt.Load(cmd.ExecuteReader());
// now sort the datatable
dt.DefaultView.Sort = "Status DESC";
// now fill out our object with each row
foreach (DataRow OneRow in dt.Rows)
{
objB.Add(new TaskListClass()
{
Id = OneRow["Id"].ToString(),
Subject = OneRow["Subject"].ToString(),
Customer = OneRow["Customer"].ToString(),
Sender = OneRow["Sender"].ToString(),
Receiver = OneRow["Receiver"].ToString(),
Priority = OneRow["Priority"].ToString(),
StartDate = OneRow["StartDate"].ToString(),
EndDate = OneRow["EndDate"].ToString(),
Status = OneRow["Status"].ToString(),
OnProgress = OneRow["OnProgress"].ToString(),
}); ;
}
}
}
}
return objB;
The way the current code is written, you could add this after the for-loop:
objB = objB.OrderByDescending(t => t.Status).ToList();
Depending of the datatype of Status, it might be sorted alphabetically.
var dataRow = dt.AsEnumerable().OrderByDescending(x => x.Field<string>("Status")).ToList();
foreach (var item in dataRow)
{
//Enter your Code Here
}
Here dt is your datatable.
dataRow is a set of list.
After get the data list, you can asign it to your "objB".

SQL query result assertion

I have put together the following method:
public static ArrayList DbQueryToArry()
{
string SqlCString = "connString";
SqlConnection connection = null;
ArrayList valuesList = new ArrayList();
connection = new SqlConnection(SqlCString);
connection.Open();
SqlCommand command = new SqlCommand("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT", connection);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
valuesList.Add(Convert.ToString(reader[0]));
}
return valuesList;
}
I'd like to be able to run an assertion like this one:
var a = DbQueryToArry();
Assert.IsTrue(a.Contains("some value"));
Given reader [0]
valuesList.Add(Convert.ToString(reader[0]));
I only get the first column (CLIENTINFO) into the array and not the second (ACCOUNT_Purpose). How should I modify the code to get both ?
In addition, the returned values could be either a String or Int so would my current code version should be handling both ?
Thanks in advance.
If we switch from obsolete ArrayList to something like IEnumerable<T>:
public static IEnumerable<IDataRecord> DbQueryToArray(string sql) {
if (null == sql)
throw new ArgumentNullException(nameof(sql));
//TODO: do not hardcode connetcion string but read it (say, from Settings)
string SqlCString = "connString";
//DONE: Wrap IDisposable into using
using (SqlConnection connection = new SqlConnection(SqlCString)) {
connection.Open();
//DONE: Wrap IDisposable into using
using (SqlCommand command = new SqlCommand(sql, connection)) {
//DONE: Wrap IDisposable into using
using (SqlDataReader reader = command.ExecuteReader()) {
while (reader.Read()) {
yield return reader as IDataRecord;
}
}
}
}
}
then you can use Linq in order to query the result:
var a = DbQueryToArray("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT");
Assert.IsTrue(a.Any(record =>
Convert.ToString(record["CLIENTNO"]) == "some value"));
Assert.IsTrue(a.Any(record =>
Convert.ToString(record["ACCOUNT_Purpose"]) == "some other value"));
If you don't want execute query several times, you can materialize the results:
var a = DbQueryToArray("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT")
.ToList();
Assert.IsTrue(a.Any(record => Convert.ToString(record[0]) == "some value"));
Assert.IsTrue(a.Any(record => Convert.ToString(record[1]) == "some other value"));
Finally (see comments below), if we want to test if any field in any record has the value:
var a = DbQueryToArray("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT")
.SelectMany(line => {
// Flatten the cursor into IEnumerable<String>
string[] result = new string[line.FieldCount];
for (int i = 0; i < result.Length; ++i)
result[i] = Convert.ToString(line[i]);
return result;
});
a.Any(item => item == "some value");
It is because you only read the first value of the reader. Reader.Read() read each row one by one and Convert.ToString(reader[0])) means that you want to read the first column as string.
That's cause you are getting only the first column. You can do something like below by specifying the column name
while (reader.Read())
{
valuesList.Add(Convert.ToString(reader["CLIENTNO"]));
valuesList.Add(Convert.ToString(reader["ACCOUNT_Purpose"]));
}
Moreover, since you are converting all the columns to string; I would suggest to use a strongly typed collection like List<string> rather then ArrayList valuesList = new ArrayList();
The other answers are good, However some concerns
Lets stop using ArrayList, and use a List<T> instead
Lets use a using statement where you can
Note : I have used a ValueTuple to return more than 1 field
Example
public static List<(string clientNo, string account)> DbQueryToArray()
{
const string SqlCString = "connString";
var valuesList = new List<(string clientNo, string account)>();
using (var connection = new SqlConnection(SqlCString))
{
connection.Open();
using (var command = new SqlCommand("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT", connection))
{
var reader = command.ExecuteReader();
while (reader.Read())
valuesList.Add(((string)reader[0],(string)reader[1]) );
}
}
return valuesList;
}
Usage
var results = DbQueryToArray();
Assert.IsTrue(results.Any(x => x.clientNo == someValue || x.account == someValue));
best practice is to check first if the reader has rows
reader.HasRows
then close the reader and the connection
your code should look like this:
public static ArrayList DbQueryToArry()
{
string SqlCString = "connString";
SqlConnection connection = null;
ArrayList valuesList = new ArrayList();
connection = new SqlConnection(SqlCString);
using (connection)
{
connection.Open();
SqlCommand command = new SqlCommand("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT", connection);
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
valuesList.Add(Convert.ToString(reader[0]));
valuesList.Add(Convert.ToString(reader[1])); // add to valuelist
}
}
reader.Close(); // close reader
} //dispose connection
return valuesList;
}
Use DataTable and SqlDataAdapter to get query result in form of table. Something like this:
string connString = #"your connection string here";
string query = "select * from table";
DataTable dataTable = new DataTable();
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(query, conn);
conn.Open();
// create data adapter
SqlDataAdapter da = new SqlDataAdapter(cmd);
// this will query your database and return the result to your datatable
da.Fill(dataTable);
conn.Close();
da.Dispose();
Then you can use dataTable object to see if particular values exist.

Is it possible to get column name only if has a value?

I am having a hard time getting the codes that I need.
The code that I have created can get ALL the column names that I have listed regardless of having a value or not. What I only wanted to happen was to get the column names that has value(1).
I have this code:
internal static List<string> GetAllRoleTypeFromDatabase()
{
List<string> rolelist = new List<string>();
using (var con = new SQLiteConnection(sqliteConnectionMain.connectionString))
using (var cmd = new SQLiteCommand(con))
{
con.Open();
var _command = con.CreateCommand();
var queryq = string.Format("SELECT * FROM tblUserRoleAccess");
string commandText = queryq;
String sSQL;
sSQL = "SELECT * from tblUserRoleAccess";
cmd.CommandText = sSQL;
var dr = cmd.ExecuteReader();
for (var i = 1; i < dr.FieldCount; i++)
{
if (dr != null)
{
rolelist.Add(dr.GetName(i));
}
}
con.Close();
}
return rolelist;
}
Read a line in the reader (as i understand, there's only one row as a result).
For each column in the row check if its value is 1 (I used int, you should use the real type i.e. bool, string ...):
dr.Read();
for (var i = 1; i < dr.FieldCount; i++)
{
if (dr.GetFieldValue<int>(i) == 1)
{
rolelist.Add(dr.GetName(i));
}
}

Converting a DataTable to my List<T>

cmd.CommandText = sql;
cmd.ExecuteNonQuery();
SqliteDataReader rdr = cmd.ExecuteReader();
dt.Load(rdr);
return dt;
DataTable tb = new DataTable(); to the List<Details> objList = new List<Details>.
foreach (var obj in dt)
{
var item = new Details();
item.ID = obj.ID;
item.Dosage = obj.Dosage;
item.Drug = obj.Drug;
item.Patient = obj.Patient;
item.Date = obj.Date;
results.Add(item);
}
the idea is here but there is an error in dt in forceach(var obj in dt)
You have to
Execute reader
Read the coursor
Create your class instances
Collect these instances into a List<T>
Something like this
// List which will be used to collect records from the query
List<MyClass> list = new List<MyClass>();
...
cmd.CommandText = sql;
//DONE: execute reader; wrap IDisposable into using
using (SqliteDataReader rdr = cmd.ExecuteReader()) {
//DONE: read each cursor's record
while (rdr.Read()) {
//DONE: Turn each record into MyClass instance
MyClass item = new MyClass();
//TODO: Assign properties
item.Property1 = Convert.ToString(rdr[0]);
item.Property2 = Convert.ToInt32(rdr[1]);
...
//DONE: Finally, collect all the intances created into the list
list.Add(item);
}
}
...

Can you get the column names from a SqlDataReader?

After connecting to the database, can I get the name of all the columns that were returned in my SqlDataReader?
var reader = cmd.ExecuteReader();
var columns = new List<string>();
for(int i=0;i<reader.FieldCount;i++)
{
columns.Add(reader.GetName(i));
}
or
var columns = Enumerable.Range(0, reader.FieldCount).Select(reader.GetName).ToList();
There is a GetName function on the SqlDataReader which accepts the column index and returns the name of the column.
Conversely, there is a GetOrdinal which takes in a column name and returns the column index.
You can get the column names from a DataReader.
Here is the important part:
for (int col = 0; col < SqlReader.FieldCount; col++)
{
Console.Write(SqlReader.GetName(col).ToString()); // Gets the column name
Console.Write(SqlReader.GetFieldType(col).ToString()); // Gets the column type
Console.Write(SqlReader.GetDataTypeName(col).ToString()); // Gets the column database type
}
Already mentioned. Just a LINQ answer:
var columns = reader.GetSchemaTable().Rows
.Cast<DataRow>()
.Select(r => (string)r["ColumnName"])
.ToList();
//Or
var columns = Enumerable.Range(0, reader.FieldCount)
.Select(reader.GetName)
.ToList();
The second one is cleaner and much faster. Even if you cache GetSchemaTable in the first approach, the querying is going to be very slow.
If you want the column names only, you can do:
List<string> columns = new List<string>();
using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SchemaOnly))
{
DataTable dt = reader.GetSchemaTable();
foreach (DataRow row in dt.Rows)
{
columns.Add(row.Field<String>("ColumnName"));
}
}
But if you only need one row, I like my AdoHelper addition. This addition is great if you have a single line query and you don't want to deal with data table in you code. It's returning a case insensitive dictionary of column names and values.
public static Dictionary<string, string> ExecuteCaseInsensitiveDictionary(string query, string connectionString, Dictionary<string, string> queryParams = null)
{
Dictionary<string, string> CaseInsensitiveDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
try
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
// Add the parameters for the SelectCommand.
if (queryParams != null)
foreach (var param in queryParams)
cmd.Parameters.AddWithValue(param.Key, param.Value);
using (SqlDataReader reader = cmd.ExecuteReader())
{
DataTable dt = new DataTable();
dt.Load(reader);
foreach (DataRow row in dt.Rows)
{
foreach (DataColumn column in dt.Columns)
{
CaseInsensitiveDictionary.Add(column.ColumnName, row[column].ToString());
}
}
}
}
conn.Close();
}
}
catch (Exception ex)
{
throw ex;
}
return CaseInsensitiveDictionary;
}
Use an extension method:
public static List<string> ColumnList(this IDataReader dataReader)
{
var columns = new List<string>();
for (int i = 0; i < dataReader.FieldCount; i++)
{
columns.Add(dataReader.GetName(i));
}
return columns;
}
For me, I would write an extension method like this:
public static string[] GetFieldNames(this SqlDataReader reader)
{
return Enumerable.Range(0, reader.FieldCount).Select(x => reader.GetName(x)).ToArray();
}
I use the GetSchemaTable method, which is exposed via the IDataReader interface.
You sure can.
protected void GetColumNames_DataReader()
{
System.Data.SqlClient.SqlConnection SqlCon = new System.Data.SqlClient.SqlConnection("server=localhost;database=northwind;trusted_connection=true");
System.Data.SqlClient.SqlCommand SqlCmd = new System.Data.SqlClient.SqlCommand("SELECT * FROM Products", SqlCon);
SqlCon.Open();
System.Data.SqlClient.SqlDataReader SqlReader = SqlCmd.ExecuteReader();
System.Int32 _columncount = SqlReader.FieldCount;
System.Web.HttpContext.Current.Response.Write("SqlDataReader Columns");
System.Web.HttpContext.Current.Response.Write(" ");
for ( System.Int32 iCol = 0; iCol < _columncount; iCol ++ )
{
System.Web.HttpContext.Current.Response.Write("Column " + iCol.ToString() + ": ");
System.Web.HttpContext.Current.Response.Write(SqlReader.GetName( iCol ).ToString());
System.Web.HttpContext.Current.Response.Write(" ");
}
}
This is originally from: http://www.dotnetjunkies.ddj.com/Article/B82A22D1-8437-4C7A-B6AA-C6C9BE9DB8A6.dcik
It is easier to achieve it in SQL
var columnsList = dbContext.Database.SqlQuery<string>("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'SCHEMA_OF_YOUE_TABLE' AND TABLE_NAME = 'YOUR_TABLE_NAME'").ToList();

Categories