I'm rewriting part of an old webforms application. I want to make a central function that will do the select queries. I will feed it the SQL query and parameters and it will do the rest.
So far I have this:
MySqlDataReader DoRead(string query, params MySqlParameter[] pms)
{
MySqlCommand myCommand;
if (!myConnection)
myConnection = new MySqlConnection(sCon);
if (myConnection.State != ConnectionState.Open)
{
myConnection.Close();
myConnection.Open();
}
myCommand = myConnection.CreateCommand();
myCommand.CommandText = query;
foreach (MySqlParameter p in pms)
{
myCommand.Parameters.Add(p);
}
return myReader = myCommand.ExecuteReader();
}
My question is, can I save the results of myReader to a variable and return that same variable instead of myReader just so I could close the connection and the reader immediately in the function instead in the main code?
Of course. And that's exactly what you should be doing, for exactly the reason you state:
so I could close the connection and the reader immediately in the function
The problem you're facing is the use of the reader in the first place, which itself is coupled to the open data stream. So if consuming code is expecting a reader, it's going to have to change in order to fix this.
How does the application later use this reader? If you're trying to make a generic function, then the result needs to be pretty generic too. So if consuming code is all doing custom things with the reader then perhaps you can instead return a DataSet and consuming code can do custom things with that instead. Something like this:
var result = new DataSet();
using(var myConnection = new MySqlConnection(sCon))
{
myConnection.Open();
var myCommand = myConnection.CreateCommand();
myCommand.CommandText = query;
foreach (var p in pms)
myCommand.Parameters.Add(p);
var myAdapter = new MySqlDataAdapter(myCommand);
myAdapter.Fill(result);
}
return result;
(Note that I made another change here as well. The connection object should also be local to the scope of the method. Shared connection objects open up a world of potential problems, one of which you've undoubtedly tried to fix with that conditional to close/open the connection state. Just avoid that world of problems entirely and dispose of connections once you're done with them.)
Related
I've been using
connection.Close();
But this is my first project in .NET and I'm not sure if I'm closing it correctly.
I don't want my website to instantly die after putting it out on real hosting.
Usually I do it this way:
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString());
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
// <some code>
conn.Open();
/ <some code>
conn.Close();
You should place the connection in a using statement;
using(var connection = new SqlConnection)
{
connection.Open();
...other code using it here.
}
The using statement ensures that the SqlConnection is disposed when you are done with it. In your scenario (depending on where you put the close method), if an exception is closed, it will never reach the Close() method and the connection will stay open.
Even better is that since you are building a website. There should be a method on the Request object (which should be available) called RegisterForDispose. This will automatically Dispose of the connection (which closes it) when the request ends. You can use it like this:
var connection = new SqlConnection();
Request.RegisterForDispose(connection);
Both accomplish the same thing in the end, but the second allows for more flexibility.
Do what Kevin said and put your .Open() call inside the using block:
using(var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()))
{
var cmd = new SqlCommand("my sql command here", conn);
conn.Open();
cmd.(whatever method you are using)
}
I am attempting to get the information of user whenever user logged in to the website, it success when I used a DataSet, but if I want to use the SqlDataReader, the error says: Invalid attempt to read when reader is closed. I have search why is it like that and I have found an article says that
SqlDataReader requires connection remains open in order to get the
data from the server, while DataSet does not need requires
connection remains open.
My question is: I want to know how can I use SqlDataReader as well? So that I don't have to depends on DataSet all the times when I want to get the data from the database.
My problem is occurs when I am trying to change the structure of reading the data function using SqlDataReader, so that it can be re-usable anytime.
Here is the code:
DatabaseManager class:
public SqlDataReader GetInformationDataReader(string procName, SqlParameter[] parameters)
{
SqlDataReader reader = null;
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(procName, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
if (parameters != null)
{
foreach(SqlParameter parameter in parameters)
{
cmd.Parameters.Add(parameter);
}
}
reader = cmd.ExecuteReader();
}
}
return reader;
}
Web Manager class:
public ModelContexts.InformationContext GetInformation(string username)
{
SqlDataReader reader = null;
ModelContexts.InformationContext context = new ModelContexts.InformationContext();
SqlParameter[] parameters =
{
new SqlParameter("#Username", SqlDbType.NVarChar, 50)
};
parameters[0].Value = username;
try
{
reader = DatabaseManager.Instance.GetInformationDataReader("GetInformation", parameters);
while(reader.Read())
{
context.FirstName = reader["FirstName"].ToString();
context.LastName = reader["LastName"].ToString();
context.Email = reader["Email"].ToString();
}
}
catch(Exception ex)
{
throw new ArgumentException(ex.Message);
}
return context;
}
Controller:
public ActionResult MainMenu(ModelContexts.InformationContext context, string firstName, string lastName, string username, string email)
{
context = WebManager.Instance.GetInformation(User.Identity.Name);
firstName = context.FirstName;
lastName = context.LastName;
username = User.Identity.Name;
email = context.Email;
return View(context);
}
Model contains string return value with getter and setter (FirstName, LastName and Email).
View contains the html label and encode for FirstName, LastName and Email from the Model.
Appreciate your answer.
Thanks.
Here is an approach you can use to keep the code pretty clean that allows you to read from the SqlDataReader while the connection is still open. It takes advantage of passing delegates. Hopefully the code is understandable. You can adjust it to fit your specific needs, but hopefully it illustrates another option at your disposal.
public void GetInformationDataReader(string procName, SqlParameter[] parameters, Action<SqlDataReader> processRow)
{
SqlDataReader reader = null;
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(procName, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
if (parameters != null)
{
foreach(SqlParameter parameter in parameters)
{
cmd.Parameters.Add(parameter);
}
}
using (SqlDataReader dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
// call delegate here.
processRow(dataReader);
}
}
}
}
return reader;
}
public ModelContexts.InformationContext GetInformation(string username)
{
SqlDataReader reader = null;
ModelContexts.InformationContext context = new ModelContexts.InformationContext();
SqlParameter[] parameters =
{
new SqlParameter("#Username", SqlDbType.NVarChar, 50)
};
parameters[0].Value = username;
try
{
// Instead of returning a reader, pass in a delegate that will perform the work
// on the data reader at the right time, and while the connection is still open.
DatabaseManager.Instance.GetInformationDataReader(
"GetInformation",
parameters,
reader => {
context.FirstName = reader["FirstName"].ToString();
context.LastName = reader["LastName"].ToString();
context.Email = reader["Email"].ToString();
});
}
catch(Exception ex)
{
throw new ArgumentException(ex.Message);
}
return context;
}
Brief explanation:
You'll notice that the overall structure of the code is very similar to what you already have. The only changes are:
Instead of returning a SqlDataReader, the GetInformationDataReader() method accepts an Action<SqlDataReader> delegate.
Within the GetInformationDataReader() method, the delegate is invoked at the correct time, while the connection is still open.
The call to GetInformationDataReader() is modified to pass in a block of code as a delegate.
This sort of pattern can be useful for exactly these cases. It makes the code reusable, it keeps it pretty clean and separate, and it doesn't prevent you from benefiting from the using construct to avoid resource/connection leaks.
You have wrapped your SqlConnection object in a using clause, therefore at the end of it SqlConnect.Dispose is called, closing the connection. Whatever caller is consuming your SqlDataReader no longer has the open connection, therefore you're getting your error.
while DataSet does not need requires connection remains open.
That is not entirely correct. DataSet is just an object that is typically filled when called by SqlDataAdapter (the Fill() method of that class). The SqlDataAdapter handles the opening and closing of the SqlConnection, which is most likely why that comment states that. But it's a different class that handles that, not the DataSet itself. Think of the DataSet as just the object that holds the result set of the SqlCommand.
To answer your comment...
So, shouldn't I use using keyword for this matter? In all of the Sql keyword?
I wouldn't take that approach either. You could have a connection leak bug quite easily with that model, and running out of pooled connections could be a not-so-fun thing to troubleshoot.
Typically it's best to consume your data and then close/dispose your connection. There's a saying, "open late, close early". That's typically how you'd want to approach this. I wouldn't try to pass a SqlDataReader object between class methods for this very issue that you're dealing with. The workaround (leaving the connection open) is very error prone.
Another though process, going back to something we mentioned, don't use the SqlDataReader. You have no benefit to cyclically loop through reading each row. Depending on your result set, just fill a DataSet (or usually more appropriate, a DataTable) and return either that Data[Set | Table] or, even better, an object that is more representative of the data it pertains to.
I've written a function to perform MySQL statements. In this function I give in a statement and get back the MySqlDataReader, but the problem is my function do not close the connection. After a short while of using the Programm, it crashs because the new connection can't be open. This is the error i got by trying open the new connection:
error connecting: Timeout expired. The timeout period elapsed prior
to obtaining a connection from the pool. This may have occurred
because all pooled connections were in use and max pool size was
reached.
My code look like this:
MySQL Class:
class mySql
{
string cs = "server=123.123.123.123;" +
"uid=abcabc;" +
"pwd=123456;" +
"database=overflow_test;";
private MySqlConnection conn_f() // create a Connection
{
MySql.Data.MySqlClient.MySqlConnection conn;
conn = new MySql.Data.MySqlClient.MySqlConnection();
conn.ConnectionString = cs;
try
{
conn.Open();
return conn;
}
catch
{
return null;
}
}
public MySqlDataReader CMD_f(string comand) //execute SQL Command
{
MySql.Data.MySqlClient.MySqlCommand cmd;
cmd = new MySql.Data.MySqlClient.MySqlCommand();
MySqlConnection conn = conn_f();
cmd.Connection = conn;
cmd.CommandText = comand;
cmd.Prepare();
rdr = cmd.ExecuteReader();
return rdr;
}
}
and an example how i use it Main Class
class main{
mySql DB = new mySql();
public void main(){
MySqlDataReader rdr = DB.CMD_f("SELECT * FROM tbl_kategorie");
int i = 0;
while (rdr.Read())
{
string str = rdr.GetString(1);
Console.WriteLine(str);
}
}
Has someone an Idea to solve the Problem.
Sincere regards LFS96 (Fabian Harmsen)
The main problem is the inbalance in the code. The CMD_f method creates a connection, but doesn't take responsibility for it. You should rather make the method that creates the connection public, so that you can take responsibility for it in the code that can close it.
That's a bigger change, so first let's look at a smaller change to fix the code.
The data reader should expose the data connection as the Connection property. I don't see that in the documentation, so it's possible that it doesn't, but the data readers in the .NET framework does.
If the property is exposed, then you can make a quick fix to make the code work with minimal change:
MySqlDataReader rdr = DB.CMD_f("SELECT * FROM tbl_kategorie");
int i = 0;
while (rdr.Read())
{
string str = rdr.GetString(1);
Console.WriteLine(str);
}
rdr.Close();
rdr.Connection.Close();
If you make the conn_f method public and don't call it in the CMD_f method, you can write much more robust code, that never leaves a connection or data reader hanging even if there is an error. That's a bigger change in the code, but definitely worth the effort when you have time to implement and test it. Using a using block to make sure that the objects are always disposed correctly makes the code much more resilient:
using (MySqlConnection conn = DB.conn_f()) {
using (MySqlDataReader rdr = DB.CMD_f(conn, "SELECT * FROM tbl_kategorie")) {
int i = 0;
while (rdr.Read()) {
string str = rdr.GetString(1);
Console.WriteLine(str);
}
}
}
Side note: Identifiers in C# tend to be descriptive. I suggest names like DB.CreateConnection and DB.ExecuteReader rather than DB.conn_f and DB.CMD_f.
I have 2 methods as below :
internal static SqlDataReader SelectData(string sql)
{
using (var sqlConnection = new SqlConnection(Constant.ConnectionString))
{
sqlConnection.Open();
var sqlCommand = new SqlCommand(sql, sqlConnection);
var dataReader = sqlCommand.ExecuteReader();
return dataReader;
}
}
============
And using this method as :
var dataReader = SelectData(---some sql ---);
private void AddData(dataReader)
{
while (dataReader.Read())
{
Employee e = new Employee();
e.FirstNamei = dataReader["Name"].ToString();
}
dataReader.Close();
}
I know we can merge this two method, but I am looking at better way write this, OR this can cause some problem??
Actually you are in fact leaving yourself open a bit. You really want to write it like this:
using (SqlConnection cnn = new SqlConnection(cnnString))
using (SqlCommand cmd = new SqlCommand(sql, cnn))
{
// use parameters in your SQL statement too, so you can do this
// and protect yourself from SQL injection, so for example
// SELECT * FROM table WHERE field1 = #parm1
cmd.Parameters.AddWithValue("#parm1", val1);
cnn.Open();
using (SqlDataReader r = cmd.ExecuteReader())
{
}
}
because you need to make sure these objects get disposed. Further, by going this direction you don't need dataReader.Close(). It will get called when it gets automatically disposed by the using statement.
Now, wrap that collection of statements inside a try...catch and you're in business.
A couple of things
1) Since you're closing your connection on SelectData, dataReader should blow up on AddData as it requires an open connection
2) AddData shouldn't close dataReader as he didn't open it.
3) Maybe you're hiding some code but I don't see that you use Employee instance created on AddData
Technically, first method would be correct if you would do
sqlCommand.ExecuteReader(CommandBehavior.CloseConnection);
Then, your client would close the reader and your connection will be closed as well.
Second example would also be correct if you didn't close the reader inside of it. There is no criminal in passing the reader to a method just to iterate it. but it has to be controlled from where it was created. How you open and dispose of it - this is different question.
I am trying to get column information in C# from a SQL table on SQL Server. I am following the example in this link: http://support.microsoft.com/kb/310107 My program strangely gets hung up when it tries to close the connection. If the connection is not closed, the program exits without any Exceptions. Here's my code:
SqlConnection connection = new SqlConnection(#"MyConnectionString");
connection.Open();
SqlCommand command = new SqlCommand("SELECT * FROM MyTable", connection);
SqlDataReader reader = command.ExecuteReader(CommandBehavior.KeyInfo); // If this is changed to CommandBehavior.SchemaOnly, the program runs fast.
DataTable table = reader.GetSchemaTable();
Console.WriteLine(table.Rows.Count);
connection.Close(); // Alternatively If this line is commented out, the program runs fast.
Putting the SqlConnection inside a using block also causes the application to hang unless CommandBehavior.KeyInfo is changed to CommandBehavior.SchemaOnly.
using (SqlConnection connection = new SqlConnection(#"MyConnectionString"))
{
connection.Open();
SqlCommand command = new SqlCommand("SELECT * FROM MyTable", connection);
SqlDataReader reader = command.ExecuteReader(CommandBehavior.KeyInfo); // If this is changed to CommandBehavior.SchemaOnly, the program runs fast even here in the using
DataTable table = reader.GetSchemaTable();
Console.WriteLine(table.Rows.Count);
}
The table in question has over 3 million rows, but since I am only obtaining the Schema information, I would think this wouldn't be an issue. My question is: Why does my application get stuck while trying to close a connection?
SOLUTION: Maybe this isn't optimal, but it does work; I inserted a command.Cancel(); statement right before Close is called on connection:
SqlConnection connection = new SqlConnection(#"MyConnectionString");
connection.Open();
SqlCommand command = new SqlCommand("SELECT * FROM MyTable", connection);
SqlDataReader reader = command.ExecuteReader(CommandBehavior.KeyInfo); // If this is changed to CommandBehavior.SchemaOnly, the program runs fast.
DataTable table = reader.GetSchemaTable();
Console.WriteLine(table.Rows.Count);
command.Cancel(); // <-- This is it.
connection.Close(); // Alternatively If this line is commented out, the program runs fast.
I saw something like this, long ago. For me, it was because I did something like:
SqlCommand command = new SqlCommand("SELECT * FROM MyTable", connection);
SqlDataReader reader = command.ExecuteReader();
// here, I started looping, reading one record at a time
// and after reading, say, 100 records, I'd break out of the loop
connection.Close(); // this would hang
The problem is that the command appears to want to complete. That is, go through the entire result set. And my result set had millions of records. It would finish ... eventually.
I solved the problem by adding a call to command.Cancel() before calling connection.Close().
See http://www.informit.com/guides/content.aspx?g=dotnet&seqNum=610 for more information.
It looks right to me overall and I think you need a little optimization. In addition to the above suggestion regarding avoiding DataReader, I will recommend to use connection pooling. You can get the details from here :
http://www.techrepublic.com/article/take-advantage-of-adonet-connection-pooling/6107854
Could you try this?
DataTable dt = new DataTable();
using(SqlConnection conn = new SqlConnection("yourConnectionString"))
{
SqlCommand cmd = new SqlCommand("SET FMTONLY ON; " + yourQueryString + "; SET FMTONLY OFF;",conn);
conn.Open();
dt.Load(cmd.ExecuteReader());
}
SET FMTONLY ON/OFF from MSDN seems the way to go
There is an specific way to do this, using SMO (SQL Server management objects)
You can get the collection of tables in the database, and then read the properties of the table you're interested in (columns, keys, and all imaginable properties)
This is what SSMS uses to get and set properties of all database objects.
Look at this references:
Database.Tables Property
Table class
This is a full example of how to get table properties:
Retrieving SQL Server 2005 Database Info Using SMO: Database Info, Table Info
This will allow you to get all the possible information from the database in a very easy way. there are plenty of samples in VB.NET and C#.
I would try something like this. This ensures all items are cleaned up - and avoids using DataReader. You don't need this unless you have unusually large amounts of data that would cause memory issues.
public void DoWork(string connectionstring)
{
DataTable dt = new DataTable("MyData");
using (var connection = new SqlConnection(connectionstring))
{
connection.Open();
string commandtext = "SELECT * FROM MyTable";
using(var adapter = new SqlDataAdapter(commandtext, connection))
{
adapter.Fill(dt);
}
connection.Close();
}
Console.WriteLine(dt.Rows.Count);
}