first time on stackoverflow.
I'm learning how to manage SqlConnection in my WebForm pages, and I want to reach the best practice to do that.
In my specific case, I have a loop and there's no way for me to run the code without errors if I don't set a new SqlConnection for every iteration of the loop (the error is about an attempt to read when reader is close).
So I declare this in the PageLoad method:
private SqlConnection con;
protected void Page_Load(object sender, EventArgs e)
{
con = new SqlConnection(connectionString);
}
Then I have this:
private int conta(int padre)
{
string SQL = "SELECT * FROM categories WHERE idp=#idpadre";
SqlCommand cd = new SqlCommand(SQL, con);
cd.Parameters.AddWithValue("#idpadre", padre);
int sub=0;
try
{
if ((con.State & ConnectionState.Open) <= 0)
{
con.Open();
}
using (SqlDataReader reader = cd.ExecuteReader())
{
while (reader.Read())
{
sub++;
}
}
}
catch (Exception err)
{
lbl.Text = "Errore conta!";
lbl.Text += err.Message;
}
finally
{
con.Close();
}
return sub;
}
protected void buildParent(int padre, int level)
{
StringBuilder sb = new StringBuilder();
sb.Append(" ");
for (int i = 0; i < level; i++)
{
sb.Append(HttpUtility.HtmlDecode(" "));
}
sb.Append("|--");
selectSQL = "SELECT * FROM categories WHERE idp=#idpadre";
SqlConnection cn = new SqlConnection(connectionString);
cmd = new SqlCommand(selectSQL, cn);
cmd.Parameters.AddWithValue("#idpadre", padre);
try
{
cn.Open();
using (SqlDataReader read = cmd.ExecuteReader())
{
while (read.Read())
{
dlParent.Items.Add(new ListItem { Text = sb.ToString() + read["cat"].ToString(), Value = read["idcat"].ToString() });
int sub = conta(Convert.ToInt32(read["idcat"]));
//int sub = 0;
if (sub > 0)
{
buildParent(Convert.ToInt32(read["idcat"]), level + 1);
}
}
read.Close();
}
}
catch (Exception err)
{
lbl.Text = "Errore buildParent!";
lbl.Text += err.Message;
}
finally
{
cn.Close();
if (s != null)
{
if (!this.IsPostBack)
{
buildPage();
buildLang();
buildImage();
}
}
}
}
In buildParent in the while loop i call "conta", but if I use the same SqlConnection (con) with both the methods I have an error about an attempt to read when reader is close.
I'm worried about the connection pool on the web server, particularly concerning the max connection reach.
So, where am I wrong? What's the best practice to manage SqlConnection?
Thank you.
You open the connection as late as possible, and you dispose as soon as possible. Let the connection pool deal with reclaiming the connections.
I usually write my code like this:
using (var conn = new SqlConnection(connectionString))
using (var cmd = new SqlCommand(commandToRun, conn))
{
cmd.Parameters.AddRange(new[]
{
new SqlParameter("myParam", "myvalue"),
new SqlParameter("myParam", "myvalue")
});
conn.Open(); // opened as late as possible
using (SqlDataReader reader = cd.ExecuteReader())
{
while (reader.Read())
{
// do stuff.
}
}
} // disposed here.
Note: To get a count from a SQL database you better use
SELECT count(*) FROM categories WHERE idp=#idpadre
And execute the query with ExecuteScalar()
Related
I need to write a C# program and it has to be able to manage my data on my server. I have an NPGSQL server set up with a data table, I can write data into it, but I just can't get to read the data while running a program.What do I do wrong?
public NpgsqlDataReader reader;
public NpgsqlCommand InsertCommand = new NpgsqlCommand();
public String sConnectionString;
public Npgsql.NpgsqlConnection Conn;
public void DataBaseOpen()
{
sConnectionString = "Server=192.168.1.100;Port=5432;Username=postgres;Password=admin;Database=analoginput;Pooling=false;MinPoolSize=1;MaxPoolSize=999;Timeout=15;";
Conn = new Npgsql.NpgsqlConnection(sConnectionString);
InsertCommand = Conn.CreateCommand();
Conn.Open();
}
public void DataBaseClose()
{
Npgsql.NpgsqlConnection.ClearAllPools();
Conn.Close();
}
InsertCommand.CommandText = "Select * From public.sensorlog WHERE \"date\" > '2019.07.08.' And \"date\" < '2019.07.10.' order by Date asc;";
System.Windows.MessageBox.Show(InsertCommand.CommandText);
Npgsql.NpgsqlDataReader reader = InsertCommand.ExecuteReader();
System.Data.DataTable CSV = new System.Data.DataTable();
while (reader.Read())
{
CSV.Load(reader);
}
I want to load the data into the CSV datatable, but I just can't get it to work. The datatable is just empty.
What if you refactor your code to something like below.
The using statement will guarantee that your connection & command is closed/disposed when it goes out of scope and with the try/catch block you will catch any exceptions and report it to the UI via messagebox. This will assist in capturing exceptions if there are any.
public Npgsql.NpgsqlConnection DatabaseOpen()
{
var sConnectionString = "Server=192.168.1.100;Port=5432;Username=postgres;Password=xxx;Database=analoginput;Pooling=false;MinPoolSize=1;MaxPoolSize=999;Timeout=15;";
var Conn = new Npgsql.NpgsqlConnection(sConnectionString);
Conn.Open();
return Conn;
}
public void Main()
{
try
{
using (var conn = DatabaseOpen())
{
using (var InsertCommand = conn.CreateCommand())
{
InsertCommand.CommandText = "Select * From public.sensorlog WHERE \"date\" > '2019.07.08.' And \"date\" < '2019.07.10.' order by Date asc;";
System.Windows.MessageBox.Show(InsertCommand.CommandText);
Npgsql.NpgsqlDataReader reader = InsertCommand.ExecuteReader();
System.Data.DataTable CSV = new System.Data.DataTable();
while (reader.Read())
{
CSV.Load(reader);
}
}
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
finally
{
Npgsql.NpgsqlConnection.ClearAllPools();
}
}
I've got a problem with some queries from c# to SQL. I need to have a query executeReader and inside of it a if else that allow me to choose between two inserts queries. I'm calling a little external program(with the URL collected into the db) which allows me to choose between 1 and 2, if the 1 is chosen(pass) else(fail). I can't do that because the debug is giving me:
'A Command is yet associated with a DataReader opened, which needs to be closed.'
I don't know what to try anymore.
private void btnSTART_Click(object sender, RoutedEventArgs e)
{
sqliteCon.Open();
if (sqliteCon.State == System.Data.ConnectionState.Open)
{
string path = null;//estrazione1
SqlCommand cmd = new SqlCommand("select nomeI FROM tabL where selection=1", sqliteCon);
SqlDataReader nomeIRdr = null;//estrazione2
//qui
var scriptsToRun = new List<string>();
using (nomeIRdr = cmd.ExecuteReader())
{
while (nomeIRdr.Read())//estrazione4
{
path = nomeIRdr["nomeI"].ToString();//estrazione5
Process MyProc = Process.Start(path);//permette la run del path contenuto nel db
MyProc.WaitForExit();
var exitCode = MyProc.ExitCode;
if (exitCode == 1)
{
scriptsToRun.Add("insert into tabL resItem values 'PASS'");
}
else
{
scriptsToRun.Add("insert into tabL resItem values 'FAIL'");
}
sqliteCon.Close();
}
}
foreach (var script in scriptsToRun)
{
SqlCommand cmd1 = new SqlCommand(script, sqliteCon);
cmd1.ExecuteNonQuery();
}
}
}
Do not share single connection and cram everything into a single routine. Please, keep your code simple.
Create (and Dispose) Connection whenever you query RDBMS
Extract methods
Code:
Process execution and return execution results collection:
// Item1 - path
// Item2 - true in succeed
private List<Tuple<string, bool>> ExecuteResults() {
List<Tuple<string, bool>> result = new List<Tuple<string, bool>>();
using (var con = new SqlConnection(ConnectionStringHere)) {
con.Open();
string sql =
#"select nomeItem
from tabList
where selection = 1";
using (SqlCommand cmd = new SqlCommand(sql, con)) {
using (var reader = cmd.ExecuteReader()) {
while (reader.Read()) {
string path = Convert.ToString(reader[0]);
using (Process process = Process.Start(path)) {
process.WaitForExit();
result.Add(Tuple.Create(path, process.ExitCode == 1));
}
}
}
}
}
return result;
}
Saving results in RDBMS
private void ApplyExecuteResults(IEnumerable<Tuple<string, bool>> results) {
using (var con = new SqlConnection(ConnectionStringHere)) {
con.Open();
string sql =
#"update tabList
set resItem = #prm_resItem
where nomeItem = #prm_nomeItem";
using (SqlCommand cmd = new SqlCommand(sql, con)) {
cmd.Parameters.Add("#prm_nomeItem", SqlDbType.VarChar);
cmd.Parameters.Add("#prm_resItem", SqlDbType.VarChar);
foreach (var item in results) {
cmd.Parameters[0].Value = item.Item1;
cmd.Parameters[1].Value = item.Item2 ? "PASS" : "FAIL";
cmd.ExecuteNonQuery();
}
}
}
}
Finally, combine both routines:
private void btnSTART_Click(object sender, RoutedEventArgs e) {
ApplyExecuteResults(ExecuteResults());
}
I have a C# application which has several methods which connect to a SQL Server database in order to execute a query.
Sometimes the connection fails and then the program exits.
A db administrator is looking on the database nevertheless I have to adapt the program in order to retry 2-3 times when a connection fails before to exiting.
I don't really know who doing this "smartly".
My connection code:
using (SqlConnection SqlCon = new SqlConnection(myParam.SqlConnectionString))
{
SqlCon.Open();
string requeteFou = "select XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
using (SqlCommand command = new SqlCommand(requeteFou, SqlCon))
{
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
// do job
}
}
}
}
}
Since I use several methods, is there a simply way to overwrite the "connection" or "read" method in order to retry the connection 3 times for example ?
Best regards
I would use Polly for retry logic.
Very basic example retrying 3 times when there is a SqlException (not tested):
static void Main(string[] args)
{
var policy = Policy
.Handle<SqlException>()
.Retry(3);
try
{
policy.Execute(() => DoSomething());
}
catch (SqlException exc)
{
// log exception
}
}
private static void DoSomething()
{
using (SqlConnection conn = new SqlConnection(""))
{
conn.Open();
string requeteFou = "select XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
using (SqlCommand command = new SqlCommand(requeteFou, conn))
{
using (SqlDataReader reader = command.ExecuteReader())
{
if (!reader.HasRows) return;
while (reader.Read())
{
// do job
}
}
}
}
}
private static function()
{
DataTable dt = new DataTable();
string connectionString = "//your connection string";
String strQuery = //"Yourquery";
const int NumberOfRetries = 3;
var retryCount = NumberOfRetries;
var success = false;
while (!success && retryCount > 0)
{
try
{
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = strQuery;
cmd.Connection = conn;
cmd.CommandTimeout = 180;
conn.Open();
SqlDataReader dr = cmd.ExecuteReader();
dt.Load(dr);
catch (Exception ex)
{
retryCount--;
Thread.Sleep(1000 * 60 * 15);
if (retryCount == 0)
{
//yourexception
}
}
}
}
Maybe wrap your using in a try block. Log a connection error in a catch block if you want. Put whole try{ }catch{ } in a for loop that will loop 3 times. If try block runs to the end of itself, break out of loop.
for(int i = 0; i < 3; i++)
{
try {
using (SqlConnection SqlCon = new SqlConnection(myParam.SqlConnectionString))
{
// your code
}
Thread.Sleep(1000); // wait some time before retry
break; // connection established, quit the loop
}
catch(Exception e) {
// do nothing or log error
}
}
You'd however have to handle differentiating SQL connection exception from other exceptions that you might encounter in your code.
When I start debugging that error shows, and it associted with the line:
textBox1.Text = cmd.ExecuteReader().ToString();
private void Form1_Load(object sender, EventArgs e)
{
SqlConnection conn = new SqlConnection(#"server= M_SHAWAF\ORCHESTRATE; integrated security=true; database=MyData");
try
{
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd = new SqlCommand(#"select MAX(Nodelevel) from Org", conn);
int s = Int32.Parse(cmd.ExecuteScalar().ToString());
for (int i = 0; i <= s; i++)
{
cmd = new SqlCommand(#"select Name from Org where NodeLevel=" + i.ToString(),conn);
textBox1.Text = cmd.ExecuteReader().ToString();
}
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
conn.Close();
}
}
How can I fix that??
You don't need to continually execute readers in order to obtain the next row of data. If all you need to do is to iterate through all row values of Name from table Org, you can execute a single Sql query to return all rows into the reader, and then to traverse the reader, e.g.:
try
{
using (var conn = new SqlConnection(#"..."))
{
conn.Open();
using (var cmd = new SqlCommand(#"select Name from Org", conn))
{
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
textBox1.Text = reader["Name"].ToString();
}
}
}
}
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
Edit, Re Hierarchical table structures
If you do need to retain separate iterators while navigating through multiple levels of a hierarchy, you will need multiple readers. As per #Philips answer, in order to have more than one active result set per SqlConnection, you'll need to enable MARS (or open multiple connections).
try
{
using (var conn = new SqlConnection(#"...;MultipleActiveResultSets=True"))
using (var cmdOuter = new SqlCommand(#"select distinct NodeLevel from Org", conn))
{
conn.Open();
using (var outerReader = cmd.ExecuteReader())
{
while (outerReader.Read())
{
var nodeLevel = reader.GetInt32(0);
Console.WriteLine("Node Level {0}", nodeLevel);
using (var cmdInner = new SqlCommand(#"select Name from Org WHERE NodeLevel = #NodeLevel", conn))
{
cmdInner.Parameters.AddWithValue("#NodeLevel", nodeLevel);
using (var innerReader = cmdInner.ExecuteReader())
{
while (innerReader.Read())
{
Console.WriteLine("Name: {0}", innerReader.GetString(0));
}
}
}
}
}
}
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
modify your connection string to allow multiple results :
connectionString="Data source=localhost; initial catalog=Interstone; integrated security=True; multipleactiveresultsets=True;"
scroll to the right for the right information ;-)
But there are lots of alternatives to avoid needing multiple queries at the same time. Each query you issue that is still pending is a resource that is being used at the server which should be minimized.
So first consider algorithms that don't require multiple cursors and if there is no alternative then setup mars.
Reader is the wrong tool
And you do have an open reader
textBox1.Text = cmd.ExecuteScalar();
First of all: I got my code running without using oop. I declared all my variables inside the same class and opened/closed the connection right before and after passing the query to the db. That worked! Now with some new experiences I tried to split my code into different classes. Now it wont work anymore.
It tells me "Connection must be valid and open". Enough text, here's my current code:
Services.cs
public static MySqlConnection conn // Returns the connection itself
{
get
{
MySqlConnection conn = new MySqlConnection(Services.ServerConnection);
return conn;
}
}
public static string ServerConnection // Returns the connectin-string
{
get
{
return String.Format("Server={0};Port=XXXX;Database=xxx;Uid=xxx;password=xxXxxXxXxxXxxXX;", key);
}
}
public static void DB_Select(string s, params List<string>[] lists)
{
try
{
MySqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
string command = s;
cmd.CommandText = command;
MySqlDataReader sqlreader = cmd.ExecuteReader();
while (sqlreader.Read())
{
if (sqlreader[0].ToString().Length > 0)
{
for (int i = 0; i < lists.Count(); i++)
{
lists[i].Add(sqlreader[i].ToString());
}
}
else
{
foreach (List<string> save in lists)
{
save.Add("/");
}
}
}
sqlreader.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error while selecting data from database!\nDetails: " + ex);
}
}
LoginForm.cs
private void checkUser(string username, string password)
{
using (Services.conn)
{
Services.conn.Open();
Services.DB_Select("..a short select statement..");
Services.conn.Close();
}
I guess this is all we need. I have shortened my code to get a focus on the problem.
I created Services.cs to get a global way to access the db from all forms without copy&pasting the connection info. Now when I reach my LoginForm.cs it throws an error "Connection must be valid and open". I've already debugged my code. It's all time closed. Even when passing conn.Open() it stays closed. Why?
Another try: I've also tried placing conn.Open() and conn.Close() inside Services.DB_Select(..) at the beginning and end. Same error here.
I have to say: The code worked before and I've used the same connection-string. So the string itself is surely valid.
I appreciate any help given here!
The problem is that you don't store the connection that was returned from your factory property. But don't use a property like a method. Instead use it in this way:
using (var con = Services.conn)
{
Services.conn.Open();
Services.DB_Select("..a short select statement..", con ));
//Services.conn.Close(); unnecessary with using
}
So use the same connection in the using that was returned from the property(or better created in the using) and pass it to the method which uses it. By the way, using a property as factory method is not best practise.
But in my opinion it's much better to create the connection where you use it, best place is in the using statement. And throw the con property to the garbage can, it is pointless and a source for nasty errors.
public static void DB_Select(string s, params List<string>[] lists)
{
try
{
using(var conn = new MySqlConnection(Services.ServerConnection))
{
conn.Open();
MySqlCommand cmd = conn.CreateCommand();
cmd.CommandText = s;
using( var sqlreader = cmd.ExecuteReader())
while (sqlreader.Read())
{
if (sqlreader[0].ToString().Length > 0)
{
for (int i = 0; i < lists.Count(); i++)
{
lists[i].Add(sqlreader[i].ToString());
}
}
else
{
foreach (List<string> save in lists)
{
save.Add("/");
}
}
} // unnecessary to close the connection
} // or the reader with the using-stetement
}
catch (Exception ex)
{
MessageBox.Show("Error while selecting data from database!\nDetails: " + ex);
}
}
Try to restructure your Services class as follows
public static MySqlConnection conn // Returns the connection itself
{
get
{
MySqlConnection conn = new MySqlConnection(Services.ServerConnection);
return conn;
}
}
private static string ServerConnection // Returns the connectin-string - PRIVATE [Improved security]
{
get
{
return String.Format("Server={0};Port=XXXX;Database=xxx;Uid=xxx;password=xxXxxXxXxxXxxXX;", key);
}
}
// Rather than executing result here, return the result to LoginForm - Future improvement
public static void DB_Select(MySqlConnection conn ,string s, params List<string>[] lists)
{
try
{
MySqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
string command = s;
cmd.CommandText = command;
MySqlDataReader sqlreader = cmd.ExecuteReader();
while (sqlreader.Read())
{
if (sqlreader[0].ToString().Length > 0)
{
for (int i = 0; i < lists.Count(); i++)
{
lists[i].Add(sqlreader[i].ToString());
}
}
else
{
foreach (List<string> save in lists)
{
save.Add("/");
}
}
}
sqlreader.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error while selecting data from database!\nDetails: " + ex);
}
}
In LoginForm.cs use returning connection and store it there. When you need to execute query, use
MySqlConnection conn=Services.conn(); // Get a new connection
Services.DB_Select(conn,"..a short select statement.."); // Executing requirement
Services.conn.Close();
Additional - I suggest you need to return MySqlDataReader to LoginForm and handle results there
private MySqlConnection _conn;
public MySqlConnection conn // Returns the connection itself
{
get
{
if(_conn == null)
_conn = new MySqlConnection(Services.ServerConnection);
return _conn;
}
}