i have created a function which executes query and returns SqlDataReader, now i am using that in another function work with the returned data, but i gets the error saying reader is already closed. here is the functions:
public static SqlDataReader ExecuteReader(string procedure, SqlParameter[] parameters, CommandType commandType)
{
SqlDataReader reader = null;
using (SqlConnection connection = new SqlConnection(connectionString))
{
using (SqlCommand command = new SqlCommand(procedure, connection))
{
connection.Open();
if(parameters != null)
{
if (commandType == CommandType.StoredProcedure)
command.Parameters.AddRange(parameters);
}
reader = command.ExecuteReader();
}
}
return reader;
}
here is the code where i am calling the SqlDataReader
using (SqlDataReader reader = SqlHelper.ExecuteReader("select top 10 username from users", null, System.Data.CommandType.Text))
{
Response.Write(reader.IsClosed); //This returns True
}
EDIT
ExecuteReader with CommanBehavior ( automatically close connection after reading data)
To over come connection closing proble just make use of CommandBheviour
- CommandBehavior.CloseConnection
When you pass above values as argument to ExecuteReader
1. there is no need to close connection explicitly connection get close when you close your reader.
code will be like this no need to close connection explicitly
public void CreateMySqlDataReader(string mySelectQuery,string myConnectionString)
{
SqlConnection myConnection = new SqlConnection(myConnectionString);
SqlCommand myCommand = new SqlCommand(mySelectQuery, myConnection);
myConnection.Open();
SqlDataReader myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
while(myReader.Read())
{
Console.WriteLine(myReader.GetString(0));
}
myReader.Close();
//Implicitly closes the connection because CommandBehavior.CloseConnection was specified.
}
its causing problem because you are closing connection
SqlReader always make use of open connection i.e live connection which is open when you use this
using (SqlConnection connection = new SqlConnection(connectionString))
{
}
it dispose connection object which is used by reader object that why its returing IsColosed as true
If you wanto return value the objec than make use of DataTable which is disconnected data object and doens makse use of connection
Modified code
public static DataTable ExecuteReader(string procedure, SqlParameter[] parameters, CommandType commandType)
{
DataTable dt = null;
using (SqlConnection connection = new SqlConnection(connectionString))
{
using (SqlCommand command = new SqlCommand(procedure, connection))
{
connection.Open();
if(parameters != null)
{
if (commandType == CommandType.StoredProcedure)
command.Parameters.AddRange(parameters);
}
SqlDataAdapter da = new SqlDataAdapter(command);
da.Fill(dt);
}
}
return dt;
}
DataReader needs an Open Connection. What you can do it either return a DataTable or Have custom class to represent the results of your SQL query and return an instance of that.
Create a Class to represent your Entity
public class Customer
{
public int ID { set;get;}
public string Name { set;get;}
}
And inside your method;
public List<Customer> GetCustomer()
{
List<Customer> custList=new List<Customer>();
using (SqlConnection connection = new SqlConnection(connectionString))
{
using (SqlCommand command = new SqlCommand("yourParameterizedSQLQuery",
connection))
{
//Add parameters if needed
connection.Open();
using (var reader = cmd.ExecuteReader())
{
if (reader.HasRows)
{
cust=new Customer();
while(reader.Read())
{
var cust=new Customer();
// TO DO :Do db null checking before reading
cust.ID=reader.GetInt32(reader.GetOrdinal("ID"));
cust.Name=reader.GetString(reader.GetOrdinal("Name"));
custList.Add(cust);
}
}
}
}
}
return custList;
}
The problem is that you have using SqlConnection which closes the connection to your database when leaving the scope.
SqlDataReader needs a "still open" connection. Returning it to the parent does not keep the connection open.
Your choice are basically to return a DataSet, which is an "unconnected" data source or change the way you manage your connection to open it, use the SqlDataReader, close the connection.
You may have to leave the connection open and let the calling code close the connection associated with the reader.
I had this challenge so I change my return type to DataTable
reader = command.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(reader);
return dt;
That way I don't have to worry about closing the connection outside that method
Related
i'm creating login and register page in MVC asp.net but problem is that for each purpose i have to do all SQl connection, command, try open etc which really makes it slow so i as wondering that if i can get rid of creating ti again and again and just create sqlconnection thing once and calling it again and again for login, registration etc
namespace LoginSys.Models
{
public class database
{
public ConnectionStatus connectDB(String name, String email, String pwd, String conStr)
{
// var con = conStr;
SqlConnection sqlCon = new SqlConnection(conStr);
SqlCommand sqlCom = new SqlCommand();
sqlCom.Connection = sqlCon;
sqlCom.CommandText = "insert into tblRegister (userName, userEmail, userPwd) values (#name, #email, #pwd)";
sqlCom.Parameters.AddWithValue("#name", name);
sqlCom.Parameters.AddWithValue("#email", email);
sqlCom.Parameters.AddWithValue("#pwd", pwd);
ConnectionStatus connectStatus = new ConnectionStatus();
int row_aff;
try
{
sqlCon.Open();
row_aff = sqlCom.ExecuteNonQuery();
connectStatus.Message = "OK";
}
catch(Exception ex)
{
connectStatus.Message = ex.Message;
}
finally
{
sqlCon.Close();
}
return connectStatus;
}
}
}
Actually you are not creating a new physical connection to the database. ADO.NET uses a connection pool. This means that even if you create a new instance of SqlConnection, you are not actually creating a new physical connection but are drawing one from the pool. Also when you call the .Close() method you are not actually closing the underlying connection. You are just returning it to the connection pool so that it can be reused.
So your code won't be slow because of that.
The only improvement I may recommend you to this code is to wrap the IDisposable objects in using statements:
public ConnectionStatus ConnectDB(string name, string email, string pwd, string conStr)
{
using (SqlConnection sqlCon = new SqlConnection(conStr))
using (SqlCommand sqlCom = sqlCon.CreateCommand())
{
sqlCon.Open();
sqlCom.CommandText = "insert into tblRegister (userName, userEmail, userPwd) values (#name, #email, #pwd)";
sqlCom.Parameters.AddWithValue("#name", name);
sqlCom.Parameters.AddWithValue("#email", email);
sqlCom.Parameters.AddWithValue("#pwd", pwd);
ConnectionStatus connectStatus = new ConnectionStatus();
try
{
sqlCom.ExecuteNonQuery();
connectStatus.Message = "OK";
}
catch(Exception ex)
{
connectStatus.Message = ex.Message;
}
return connectStatus;
}
}
I have a method inside a class, that contains SqlDataReader(it connects to a database table and retrieves a number of rows).
later i want to assign each record that SqlDataReader has, to asp labels . but i don't know what should be the return type of a method , so i can extract the values from it, and how to do that in code behind file.
here is the code;
public (???) displayCustoemrhShipOrder()
{
string htmlStr = string.Empty;
string sConnectionString = ConfigurationManager.ConnectionStrings["LGDB"].ToString();
SqlConnection SqlCOn = new SqlConnection(sConnectionString);
SqlCommand SqlCmd = new SqlCommand();
SqlCmd.Connection = SqlCOn;
SqlCmd.CommandText = "displayCustomerShipOrder";
SqlCmd.CommandType = CommandType.StoredProcedure;
SqlCOn.Open();
SqlCmd.Parameters.AddWithValue("ShipOrderID",shipOrderID);
SqlDataReader reader = SqlCmd.ExecuteReader();
while (reader.Read())
{
htmlStr = reader.GetInt32(0).ToString();
}
reader.Close();
SqlCOn.Close();
return(???)
}
In general, you should never return SqlDataReader to a layer outside of your database-access code. The reason behind this general principle is that SqlDataReader is a very "expensive" object that keeps an open connection to the database! So if you keep a SqlDataReader around, it's not so bad, but if you keep 10 lying around at any given time? what about 100 of them? 1000? It's a recipe for disaster.
So how do you close the connection to the database from a SqlDataReader? .NET has a very handy methods in the IDisposable interface called Dispose() that will take care of the cleanup for you. So your code might looks like
function getData()
{
// instantiate SqlDataReader from SqlCommand, call it "rdr"
rdr.Dispose();
}
But actually there's an issue with that code. What if your code throws an exception before it gets to Dispose()? It should really look like
function getData()
{
try{
// instantiate SqlDataReader from SqlCommand, call it "rdr"
}
catch(Exception){}
Finally{
rdr.Dispose();
}
}
That's very verbose! What if you don't want to type all that? Don't worry, .NET handled this for you as well. Do this:
public (???) displayCustoemrhShipOrder()
{
string htmlStr = string.Empty;
string sConnectionString = ConfigurationManager.ConnectionStrings["LGDB"].ToString();
using(SqlConnection SqlCOn = new SqlConnection(sConnectionString))
{
using(SqlCommand SqlCmd = new SqlCommand())
{
SqlCmd.Connection = SqlCOn;
SqlCmd.CommandText = "displayCustomerShipOrder";
SqlCmd.CommandType = CommandType.StoredProcedure;
SqlCOn.Open();
SqlCmd.Parameters.AddWithValue("ShipOrderID",shipOrderID);
using(SqlDataReader reader = SqlCmd.ExecuteReader())
while (reader.Read())
{
htmlStr = reader.GetInt32(0).ToString();
}
// reader.Close(); // this line becomes optional, Dispose() will call Close()
}
// SqlCOn.Close(); // this line becomes optional, Dispose() will call Close()
}
}
return(???)
}
This is all a roundabout way of saying that if you know you need to Dispose() and Close() the SqlDataReader, and that you cannot get data out of it once you do that, then you obviously should not return it to code what needs to work with the data (but not necessarily the database connection that it was taking up). I suggest using a DataTable class, you can read about it here:
.NET DataTable class
Also the method to convert to a DataTable is called DataTable.Load(SqlDatareader...
You can add each individual value to List<string> return it and use outside of the method.
I am trying to create a web service which will help to execute stored procedure. And that web method I am calling in my code to execute a stored procedure. This is my web method -
[WebMethod(Description = des_ExecuteParamerizedSelectCommand)]
public DataTable ExecuteParamerizedSelectCommand(string CommandName, CommandType cmdType, SqlParameter[] param)
{
DataTable table = new DataTable();
using (SqlConnection con = new SqlConnection(ConnectionString()))
{
using (SqlCommand cmd = con.CreateCommand())
{
cmd.CommandType = cmdType;
cmd.CommandText = CommandName;
cmd.Parameters.AddRange(param);
try
{
if (con.State != ConnectionState.Open)
{
con.Open();
}
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(table);
}
}
catch
{
throw;
}
}
}
return table;
}
Now this is my code in my data access layer - when I am trying to call this web method, its throwing compile time error.
Error 2 Argument 2: cannot convert from 'System.Data.CommandType' to 'DAL.sqlDBHelper.CommandType'
Error 3 Argument 3: cannot convert from 'System.Data.SqlClient.SqlParameter[]' to 'DAL.sqlDBHelper.SqlParameter[]'
My code to call the webmethod -
sqlDBHelper.ODCdbHelper mysqlDBHelper = new sqlDBHelper.ODCdbHelper();
public Login GetUserRoles(string _Idsid)
{
Login login = null;
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter("#UserName", _Idsid)
};
//Lets get the list of all employees in a datataable
using (DataTable table = mysqlDBHelper.ExecuteParamerizedSelectCommand("GetUserRole", CommandType.StoredProcedure, parameters))
Can you please tell me someone, where I am wrong??
Thanks in advance
Gulrej
Try like this
DAL.sqlDBHelper.SqlParameter[] parameters = new DAL.sqlDBHelper.SqlParameter[]//Change Here {
SqlParameter("#UserName", _Idsid)
};
//Lets get the list of all employees in a datataable
using (DataTable table = mysqlDBHelper.ExecuteParamerizedSelectCommand("GetUserRole", DAL.sqlDBHelper.CommandType.StoredProcedure, parameters))
I presume DAL.sqlDBHelper.CommandType will be an enumerator in your data access layer.
And the expected parameter is DAL.sqlDBHelper.SqlParameter[] instead of System.Data.SqlClient.SqlParameter[]
So you might call the select function as
DAL.sqlDBHelper.SqlParameter[] parameters = new DAL.sqlDBHelper.SqlParameter[]
{
new SqlParameter("#UserName", _Idsid)
};
using (DataTable table = mysqlDBHelper.ExecuteParamerizedSelectCommand("GetUserRole", DAL.sqlDBHelper.CommandType.StoredProcedure, parameters))
Please check what is the command type defined for stored procedures in your DAL.
I'm kind of struggling trying to get this datagrid to bind. Everytime I run my code, I get an error message stating, "Invalid attempt to call Read when reader is closed". I don't see where I am closing my reader. Can you please help me? My code for loading the datagrid is below:
protected void LoadGrid()
{
using (SqlConnection conn = new SqlConnection())
{
conn.ConnectionString = ConfigurationManager.ConnectionStrings["VTC"].ConnectionString;
conn.Open();
string sql = "select * from roi_tracking";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
using (SqlDataReader sqlReader = cmd.ExecuteReader())
{
gridROI.DataSource = sqlReader;
gridROI.DataBind();
sqlReader.Dispose();
cmd.Dispose();
}
}
}
}
You can't use a SqlDataReader as a DataSource for a DataGrid.
From MSDN:
A data source must be a collection that implements either the
System.Collections.IEnumerable interface (such as
System.Data.DataView, System.Collections.ArrayList, or
System.Collections.Generic.List(Of T)) or the IListSource interface to
bind to a control derived from the BaseDataList class.
Datasource property:
http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.basedatalist.datasource.aspx
SqlDataReader:
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.aspx
A common methodology to bind the query results to your datagrid would be to use a SqlDataAdapter to fill a DataTable or DataSet and then bind that to your DataGrid.DataSource:
protected void LoadGrid()
{
using (SqlConnection conn = new SqlConnection())
{
conn.ConnectionString = ConfigurationManager.ConnectionStrings["VTC"].ConnectionString;
conn.Open();
string sql = "select * from roi_tracking";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = cmd;
adapter.Fill((DataTable)results);
gridROI.DataSource = results;
}
}
}
In addition to what pseudocoder said, you wouldn't want to bind to a SqlDataReader anyway: the connection to the database will remain open so long as the reader instance exists.
You definitely want to deserialize the data into some other disconnected data structure so that you release the connection back into the pool as quickly as possible.
I have a generic method to call a stored Procedure in ASP.NET:
public SqlDataReader ExecuteStoredProc(string sprocName, SqlParameter[] SqlP)
{
SqlDataReader iReader;
SqlCommand sql = new SqlCommand();
sql.CommandText = sprocName;
sql.CommandType = CommandType.StoredProcedure;
sql.Connection = ConnStr;
if (SqlP != null)
{
foreach (SqlParameter p in SqlP)
{
sql.Parameters.Add(p);
}
}
sql.Connection.Open();
iReader = sql.ExecuteReader(CommandBehavior.CloseConnection);
sql.Dispose();
return iReader;
}
Even though I am calling CommandBehavior.CloseConnection the connection is not closing. I can get the data fine the first time I request a page. On reload I get the following error:
The connection was not closed. The
connection's current state is open.
Description: An unhandled exception
occurred during the execution of the
current web request. Please review the
stack trace for more information about
the error and where it originated in
the code.
Exception Details:
System.InvalidOperationException: The
connection was not closed. The
connection's current state is open.
Source Error:
Line 35: Line 36: } Line
37: sql.Connection.Open();
Line 38: iReader =
sql.ExecuteReader(CommandBehavior.CloseConnection);
Line 39: sql.Dispose();
Finally if I put sql.Connection.Close(); before sql.Dispose(); I get an error that iReader is not readable because it's been closed already.
Obviously I am closing my connection incorrectly, can someone point me in the right direction?
When you return a DataReader, the underlying connection must remain open. It's the consumer's responsibility to properly clean up resources.
public SqlDataReader ExecuteStoredProc(string sprocName, SqlParameter[] SqlP)
{
SqlCommand sql = new SqlCommand();
sql.CommandText = sprocName;
sql.CommandType = CommandType.StoredProcedure;
sql.Connection = ConnStr;
if (SqlP != null)
{
foreach (SqlParameter p in SqlP)
{
sql.Parameters.Add(p);
}
}
sql.Connection.Open();
return sql.ExecuteReader(CommandBehavior.CloseConnection);
}
public void ConsumingMethod()
{
using(SqlDataReader reader = ExecuteStoredProc("MyProc", params))
{
while(reader.Read())
{
//work with your reader
}
}
}
I would suggest wrap the sql connection with a "using" statement, and that will take care of most sql connection issue.
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "...";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
// ...
}
}
}
}
The idea is to do a Connection.Close(); after you've finished with the SqlReader, so basically instead of placing the close() statement before the SqlReader.Dispose() command, you should place it below.
This is my preferred way to process IDataReader. Let the caller creates SqlConnection instance and passes to methods.
It's expensive to create SqlConnection instance. And you’ll end up code call the same ExecuteStoredProc method multiple times in different situation.
Thus, I refactor ExecuteStoredProc method by adding SqlConnection instance as part of the parameter.
using (SqlConnection conn = new SqlConnection())
{
conn.ConnectionString = // Connection String;
conn.Open();
using (IDataReader reader = foo.ExecuteStoredProc(conn, sprocName, SqlP))
{
// Process IDataReader
}
}