System.InvalidOperationException: 'Invalid attempt to call FieldCount when reader is closed.' - c#

I have this class that tries to read from the sql reader
using Microsoft.Data.SqlClient;
using MinimalAPI.Models.ConnectUI;
namespace ConnectAPI.SQL.BAL
{
public class ConnectData
{
public static Form Get_Froms_Data(int form_Id, string page_Id = null)
{
Form form = new Form();
SqlDataReader reader = ConnectAPI.SQL.DAL.ConnectData.Get_Froms_Data(form_Id, page_Id);
foreach(var row in reader) <-- where the error occurs
{
var t = "";
}
reader.Close();
return form;
}
}
}
And this class to access dal call:
using Microsoft.Data.SqlClient;
using MinimalAPI.Models.ConnectUI;
using System.Data;
namespace ConnectAPI.SQL.DAL
{
public class ConnectData
{
static string sqlconnectionstring = "xxx";
public static SqlDataReader Get_Froms_Data(int form_Id, string page_Id = null)
{
string procedurename = "getFormData";
SqlDataReader reader = null;
using (SqlConnection con = new SqlConnection(sqlconnectionstring))
{
con.Open();
using (SqlCommand cmd = new SqlCommand(procedurename, con))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add("#formId", SqlDbType.Int).Value = form_Id;
cmd.Parameters.Add("#pageId", SqlDbType.NVarChar, 50).Value = page_Id;
reader = cmd.ExecuteReader();
}
con.Close();
}
return reader;
}
}
}
but i keep getting the error:
System.InvalidOperationException: 'Invalid attempt to call FieldCount when reader is closed.'

The reader needs an open connection. Therefore, closing the connection effectively closes the reader, too. To get around it, you can start by changing it to return IEnumerable<IDataRecord> via an iterator block, like this:
public class ConnectData
{
static string sqlconnectionstring = "xxx";
public static IEnumerable<IDataRecord> Get_Froms_Data(int form_Id, string page_Id = null)
{
string procedurename = "getFormData";
using var con = new SqlConnection(sqlconnectionstring);
using var cmd = new SqlCommand(procedurename, con);
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add("#formId", SqlDbType.Int).Value = form_Id;
cmd.Parameters.Add("#pageId", SqlDbType.NVarChar, 50).Value = page_Id;
con.Open();
using var reader = cmd.ExecuteReader();
while (rdr.Read())
{
yield return reader;
}
}
}
But there is still an issue here, in that we are yielding the same object over and over as it mutates. This can have some strange or unexpected consequences. We can fix this by making sure to construct a new object before returning to other code and yielding the new object. We can also improve this to reduce the amount of boilerplate needed for each method.
public class ConnectData
{
static string sqlconnectionstring = "xxx";
// notice this is private
private static IEnumerable<IDataRecord> GetSQLData(string sql, Action<SqlParameterCollection> addParams, bool AsProcedure = true)
{
using var con = new SqlConnection(sqlconnectionstring);
using var cmd = new SqlCommand(sql, con);
if (AsProcedure) cmd.CommandType = System.Data.CommandType.StoredProcedure;
if (addParams is object) addParams(cmd.Parameters);
con.Open();
using var reader = cmd.ExecuteReader();
while (rdr.Read())
{
yield return reader;
}
}
// I don't know what kind of item you're returning here, had to make it up
public static IEnumerable<FormItem> Get_Forms_Data(int form_Id, string page_Id = null)
{
string procedurename = "getFormData";
var data = GetSqlData(procedureName, p => {
p.Add("#formId", SqlDbType.Int).Value = form_Id;
p.Add("#pageId", SqlDbType.NVarChar, 50).Value = page_Id;
}, true);
foreach(var row in data)
{
// again: I don't know what your records look like
yield return new FormItem( row["column"]);
}
}
}

Related

How can I return multiple string values using while loop+

This is what I want:
A method that is within a class that will return iteratively the values of a certain column. This values will the be added to a combobox when the method is invoked. Here is my attempt:
public string FillCombo()
{
string connstring = "Data Source=HP\\SQLEXPRESS;Initial Catalog=Arana;Integrated Security=True";
string query = "Select * from categorias";
SqlConnection conn = new SqlConnection(connstring);
SqlCommand command = new SqlCommand(query, conn);
SqlDataReader read;
conn.Open();
read = command.ExecuteReader();
while (read.Read())
{
string combodata = read.GetString(1);
return (combodata);
}
return null;
}
however, when this method is invoked, it only returns the first row into de combobox, not the other values.
It's called yield
http://msdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx
From the manual
public static System.Collections.IEnumerable Power(int number, int exponent)
{
int result = 1;
for (int i = 0; i < exponent; i++)
{
result = result * number;
yield return result;
}
}
yield will send a collection of return results from inside a loop after the loop has completed.
You can close data connections using a try/finally block around the loop.
public IEnumerable FillCombo()
{
SqlConnection conn = new SqlConnection(connstring);
SqlCommand command = new SqlCommand(query, conn);
SqlDataReader read;
conn.Open();
read = command.ExecuteReader();
try
{
while (read.Read())
{
yield return read.GetString(1);
}
}
finally
{
read.close();
conn.close();
}
}
A cool and often overlooked feature of C#
Consider using a List of string as an output. The following minor change to your code should help...
public List<string> FillCombo()
{
List<string> comboList = new List<string>();
string connstring = "Data Source=HP\\SQLEXPRESS;Initial Catalog=Arana;Integrated Security=True";
string query = "Select * from categorias";
SqlConnection conn = new SqlConnection(connstring);
SqlCommand command = new SqlCommand(query, conn);
SqlDataReader read;
conn.Open();
read = command.ExecuteReader();
while (read.Read())
{
string combodata = read.GetString(1);
comboList.Add(combodata);
}
return comboList;
}
Good Luck!

Returning result of SqlCommand in class

As I'm not programming long time I would like to ask you if there is way to call result of this SqlCommand which is in class called klientClass
I was thinking that it could look something like this:
private static void ReadFirma()
{
string queryString =
"SELECT rocnik from FIRMA;";
using (SqlConnection connection = new SqlConnection(myConnection.DataSource.ConnectionString
))
{
SqlCommand command = new SqlCommand(
queryString, connection);
connection.Open();
int result= Convert.ToInt32(command.ExecuteScalar());
try
{
}
finally
{
reader.Close();
}
}
}
Because I need to insert this result into my report parameter here:
this.klientTableAdapter.Fill(this.prehled_zajezdu.HereReturnResult);
this.reportViewer1.RefreshReport();
I'm sorry for quiet low-quality question, hope not to receive down-votes.
This is how you can retrieve and use the value from the database in your Fill method (provided that the Fill method takes an argument of the type int and that the myConnection field is available from the static method)
private static int ReadFirma()
{
string queryString = "SELECT rocnik from FIRMA";
using (var connection =
new SqlConnection(myConnection.DataSource.ConnectionString))
using(var command = new SqlCommand(queryString, connection))
{
connection.Open();
return Convert.ToInt32(command.ExecuteScalar());
}
}
void SomeMethod()
{
this.klientTableAdapter.Fill(ReadFirma());
}
You can use DataTable object for your Goal.
private static DataTable ReadFirma()
{
string queryString = "SELECT rocnik from FIRMA";
using (var connection =
new SqlConnection(myConnection.DataSource.ConnectionString))
using(var command = new SqlCommand(queryString, connection))
{
connection.Open();
DataTable dt = new DataTable();
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = command;
da.Fill(dt);
return dt;
}
}
void SomeMethod()
{
this.klientTableAdapter.Fill(ReadFirma());
}

simplify OleDbDataReader code

I made a program, that reads data in the database I use the OleDbDataReader but the problem is I have different tables, this codes works perfectly but I found it a little bit "hardcoded" or recursive here is my sample code
private void loadMilk()
{
cn.Open();
OleDbDataReader reader = null;
OleDbCommand cmd = new OleDbCommand("select* from Milk", cn);
reader = cmd.ExecuteReader();
while (reader.Read())
{
Milk.Add(reader["Product"].ToString());
}
cn.Close();
}
I need to repeat this again and again just to read what's on the other table (e.g., "select* from Fruit then "select* from Classics....) Is there any way so that I will not repeat this code again and again?
thanks.:)
You can refactor that method into something like this:
private IList<string> Load(string tableName, string columnName)
{
var result = new List<string>();
cn.Open();
OleDbDataReader reader = null;
OleDbCommand cmd = new OleDbCommand(string.Format("select* from {0}", tableName), cn);
reader = cmd.ExecuteReader();
while (reader.Read())
{
result.Add(reader[columnName].ToString());
}
cn.Close();
return result;
}
Your code sample will be:
var milkItems = Load("Milk", "Product");
var classicItems = Load("Classics", "..."); //Enter the column here.
Edit:
You might want something a little more specific (eg. storing a List<SomeObject> instead of just List<string>). Let's suppose you sometimes you want to return a list of Person, and also you want to read a list of Building. Then you can write something like this (not compiled & tested):
private IList<T> Load<T>(string tableName, Func<OleDbDataReader, T> selector)
{
IList<T> result = new List<T>();
cn.Open();
OleDbDataReader reader = null;
OleDbCommand cmd = new OleDbCommand(string.Format("select* from {0}", tableName), cn);
reader = cmd.ExecuteReader();
while (reader.Read())
{
result.Add(selector(reader));
}
cn.Close();
return result;
}
and you can call it like:
Func<OleDbDataReader, Person> selector = x => new Person { Name = x["Person"].ToString() };
Load("People", selector);
private void loadMilk(string TableName, string itemValue)
{
string SQLString = String.Format("select * from {0}",TableName);
cn.Open();
OleDbDataReader reader = null;
OleDbCommand cmd = new OleDbCommand(SQLString, cn);
reader = cmd.ExecuteReader();
while (reader.Read())
{
Milk.Add(reader[ItemValue].ToString());
}
cn.Close();
}
Not sure what type "Milk" is.
Try:
private void loadObjectsFrom(string tableName, object obj, string column)
{
cn.Open();
OleDbDataReader reader = null;
OleDbCommand cmd = new OleDbCommand("select* from " + tableName, cn);
reader = cmd.ExecuteReader();
while (reader.Read())
{
obj.Add(reader[column].ToString());
}
cn.Close();
}
Just pass a table name as a parameter:
private void loadMilk(string tableName)
{
cn.Open();
OleDbDataReader reader = null;
OleDbCommand cmd = new OleDbCommand(string.Format("select* from {0}",tableName), cn);
reader = cmd.ExecuteReader();
while (reader.Read())
{
Milk.Add(reader["Product"].ToString());
}
cn.Close();
}

c# - Fill generic list from SqlDataReader

How can I add values that a SqlDataReader returns to a generic List? I have a method where I use SqlDataReader to get CategoryID from a Category table. I would like to add all the CategoryID a generic List.
This dose not work because it returns only one categoryID and that is the last one. I want to add all the categoryID to the list and then return them.
How do I do that?
SqlConnection connection = null;
SqlDataReader reader = null;
SqlCommand cmd = null;
try
{
connection = new SqlConnection(connectionString);
cmd = new SqlCommand("select CategoryID from Categories", connection );
connection.Open();
List<int> catID = new List<int>();
dr = cmd.ExecuteReader();
while (dr.Read())
{
catID.Add(Convert.ToInt32(dr["CategoryID"].ToString()));
}
}
finally
{
if (connection != null)
connection.Close();
}
return catID;
Try like this, it's better, safer, uses lazy loading, less code, working, ...:
public IEnumerable<int> GetIds()
{
using (var connection = new SqlConnection(connectionString))
using (var cmd = connection.CreateCommand())
{
connection.Open();
cmd.CommandText = "select CategoryID from Categories";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
yield return reader.GetInt32(reader.GetOrdinal("CategoryID"));
}
}
}
}
and then:
List<int> catIds = GetIds().ToList();
Your current code should work, assuming catID is really declared before the try block, otherwise this won't compile.
AS BrokenGlass explained this is the demonstration
SqlConnection connection = null;
SqlDataReader dr= null;
SqlCommand cmd = null;
List<int> catID = new List<int>();
try
{
connection = new SqlConnection(connectionString);
cmd = new SqlCommand("select CategoryID from Categories", connection );
connection.Open();
dr = cmd.ExecuteReader();
while (dr.Read())
{
catID.Add(Convert.ToInt32(dr["CategoryID"].ToString()));
}
}
finally
{
if (connection != null)
connection.Close();
}
return catID;
as well as you change the declaration
SqlDataReader reader = null;
to
SqlDataReader dr= null; // Because you are using dr in the code not reader
This should work but I suggest you to use using with your connections
SqlConnection connection = null;
SqlDataReader reader = null;
SqlCommand cmd = null;
List<int> catID = new List<int>();
try
{
connection = new SqlConnection(connectionString);
cmd = new SqlCommand("select CategoryID from Categories", connection );
connection.Open();
dr = cmd.ExecuteReader();
while (dr.Read())
{
catID.Add(Convert.ToInt32(dr["CategoryID"].ToString()));
}
}
finally
{
if (connection != null)
connection.Close();
}
return catID;
List<int> s = new List<int>();
conn.Open();
SqlCommand command2 = conn.CreateCommand();
command2.CommandText = ("select turn from Vehicle where Pagged='YES'");
command2.CommandType = CommandType.Text;
SqlDataReader reader4 = command2.ExecuteReader();
while (reader4.Read())
{
s.Add(Convert.ToInt32((reader4["turn"]).ToString()));
}
conn.Close();

How to bind a datasource to a label control

It's easy to bind a data source to something like a gridview or repeater, but how would I do it with a label? Heres the sql connection that I want to modify. By the way, I don't need 2 way binding.
public void Sql_Connection(string queryString)
{
SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["RBConnectionString"].ConnectionString);
SqlCommand cmd = new SqlCommand(queryString, conn);
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
}
The query I'm using:
SELECT Description FROM RbSpecials WHERE Active=1
public string SqlConnection(string queryString)
{
using (var conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["RBConnectionString"].ConnectionString))
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = queryString;
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
// This will return the first result
// but there might be other
return reader.GetString(0);
}
}
return null;
}
}
This will also ensure that in case of exception all disposable objects are disposed and will properly return the SQLConnection to the connection pool in order to be reused.
And finally assign the Text property of the label:
lblTest.Text = SqlConnection("SELECT Description FROM RbSpecials WHERE Active=1");
use ExecuteReader rather than ExecuteNonQuery
public void Sql_Connection(string queryString)
{
using(SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings"RBConnectionString"].ConnectionString))
{
using(SqlCommand cmd = new SqlCommand(queryString, conn))
{
conn.Open();
using(SqlDataReader rdr = cmd.ExecuteReader())
{
while(rdr.Read())
{
lblDescription.Text = rdr.GetString(0);
}
}
}
}
}
using (SqlConnection con = new SqlConnection(Connection_String))
{
SqlCommand cmd = new SqlCommand("select * from Customers", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataReader adpt = cmd.ExecureReader();
if(rdr.Read())
{
lblName.Text = rdr[0].ToString();
}
}

Categories