I call a function which returns a SqlDataReader object to calling statement. I am confused where should I close the SqlDataReader object and SqlConnection object? In function or after calling it?
This is the function call:
SqlDataReader dr2= RetrieveSearcher();
pid = dr2[0].ToString();
This is the function:
protected SqlDataReader RetrieveSearcher()
{
String Q = "select price from tb3 where pid='12';
cn = new SqlConnection("data source=.\\sqlexpress; integrated security=true; initial catalog=singh");
cn.Open();
cmd = new SqlCommand(Q,cn);
dr1 = cmd.ExecuteReader();
dr1.Read();
return dr1;
}
Always use parameterized queries to avoid sql injection attacks and increase performance (most db servers can reuse execution plans with proper queries)
Never leave a connection open any longer than necessary!
Do not share db connections! Create it, use it, destroy it.
Wrap everything that implements IDisposable in a using block like Connections, Commands, DataReaders, etc. This ensures no resources remain open even in the event of an exception.
Use correct types in your db schema and read those types, do not blanket-convert everything to/from string! Example price seems like it should really be a decimal or numeric value and not a string so do not store it as a string and do not read it back as a string.
Retrieve the connection strings by name from the app.config or web.config (depending on the application type), do not hard code the strings into your connections or anywhere else.
About your logic
Change your method to return a custom type like a piece of data. This ensures proper SoS (Separation of Concerns). Do not return a DataReader! This will abstract the whole database call from the caller which is what you should strive for.
protected SomeType RetrieveSearcherData(string pid)
{
const string Q = "SELECT price FROM tb3 WHERE pid = #pid";
using(var cn=new SqlConnection())
using(var cmd=new SqlCommand(Q,cn))
{
// I do not know what pid is but use tho correct type here as well and specify that type using SqlDbType
cmd.Parameters.Add(new SqlParameter("#pid", SqlDbType.VarChar, 100) { Value = pid});
cn.Open();
using(var dr1= cmd.ExecuteReader())
{
if(dr1.Read())
{
var result = dr1.GetDecimal(0);
// read something and return it either in raw format or in some object (use a custom type)
}
else
return null; // return something else that indicates nothing was found
}
}
}
Do you really want to open a connection each time you call into this function? Having one thread deal with multiple connections is a sure fire way to get deadlocks.
If you still want to do #1, I'd recommend having your RetrieveSearcher return the data it needs in a List<T> or heck, just return a DataTable and deal with that. That way the function can close the connection that it opened.
If you still REALLY want to return a SqlDataReader then you need to make sure that you can close the connection that you opened. SqlDataReader doesn't expose a SqlConnection directly, so you can't directly close the connection after you leave the RetrieveSearcher method. However, you can do this:
dr1 = cmd.ExecuteReader(CommandBehavior.CloseConnection);
That will close the connection when the reader is closed. So, then you can do:
using (SqlDataReader dr2 = RetrieveSearcher()) {
pid=dr2[0].ToString();
}
I'm assuming of course that you REALLY need more than just one string. :) If you REALLY only need one string you just be returning the string and calling cmd.ExecuteScalar();
Related
Such as so: Creating it in a method and assigning it to a field. Passing that field to a method and then assigning it to a variable of a using-statement (Which is the only Dispose being called).
SqlCommand CreateSqlCommand()
{
SqlCommand cmd1 = new SqlCommand();
return cmd1;
}
void UseSqlCommand(SqlCommand cmd4)
{
using (SqlCommand cmd3 = cmd4)//Is this using-statement enough?
{
//use cmd3 here...
}
}
And used:
SqlCommand cmd2 = CreateSqlCommand();
UseSqlCommand(cmd2);
Extra detail: Will the GC collect all of these variables on its next round or not? Why not - see David M. Kean's answer here.
EDIT
I've added
cmd2.CommandText = "";
after the previous (last) line. And there's no error thrown.
Why? It should be disposed already! Never mind. A disposed object can be referenced...
Please do not concentrate on the example, rather do - on the question itself. Thanks.
Yes, the using statement will call Dispose() after the block has completed on the referenced variable. This is a good and bad thing, suppose that you create a sql command and store the result in a variable cmd, then you pass that variable to another method that uses and disposes cmd. Now you are stuck with a variable that is disposed, and if you try to use it, it might throw an ObjectDisposedException.
SqlCommand cmd = CreateSqlCommand();
UseSqlCommand(cmd);
//Uh oh, cmd can still be used, what if I tried to call UseSqlCommand(cmd) again?
It would be more clear and secure to dispose of that object outside of the method(Like Jordão posted).
using(SqlCommand cmd = CreateSqlCommand())
{
UseSqlCommand(cmd);
}
Now you completely control the object and limit it's scope.
Control the scope of using from outside:
using (SqlCommand cmd2 = CreateSqlCommand()) {
UseSqlCommand(cmd2);
}
...
void UseSqlCommand(SqlCommand cmd4) {
// use cmd4 here...
}
And maybe rename UseSqlCommand to something different, like ExecuteSqlCommand.
The purpose of the using statement is not to dispose of variables, but rather object instances. Variables are often used for the purpose of identifying object instances, but reference-type variables don't hold object--they hold "object identifiers".
If one says e.g. var myPort = new System.Io.Ports.SerialPort("COM1", ...); myPort.Open(), the SerialPort object will ask the system to let it use the COM1 serial port and not let anyone else use it until further notice. The system will generate a handle for that port, and set some flags so that only code which has that handle will be allowed to use the port. Once the object is created (say the system arbitrarily assigns it an ID of #8675309), the system will store that ID into the variable myPort).
When code no longer needs to use that serial port, it is important that someone tell object #8675309 that it is no longer needed, so it can in turn tell the system that it should make COM1 available to other applications. This would typically be done by calling Dispose on a variable which holds a reference to object #8675309. Once that is done, every variable that holds a reference to object #8675309 will hold a reference to an object whose Dispose method has been called. Note that the Dispose method won't actually affect any of those variables (unless they are rewritten within the code of the method itself). Any variables which held "object #8675309" before the call will continue to do so after. The object will have released its serial port, so the reference which is stored those variables will no longer be useful for much, and the code that uses those variables may want them to be cleared, but the SerialPort object won't care one way or the other.
I think this is what you're trying to do:
public class MySqlClass : IDisposable
{
private SqlConnection conn { get; set; }
public MySqlClass(string connectionstring)
{
conn = new SqlConnection(connectionstring);
}
public void DoSomething1(string tsql)
{
using (SqlCommand comm = new SqlCommand(tsql, conn)) {
conn.Open();
}
}
public void DoSomething2(string tsql)
{
using (SqlCommand comm = new SqlCommand(tsql, conn)) {
conn.Open();
}
}
//DISPOSE STUFF HERE
}
Use...
using (MySqlClass MySQL = new MySqlClass())
{
MySQL.DoSomething1();
MySQL.DoSomething2();
}
* UPDATE *
Updated >>>> EXAMPLE <<<<< Point is you create single instance of SqlConnection above, and you can resuse it. The class implements IDisposable so you can use the using() method to auto dispose. Better than passing instances of SqlCommand like you mentioned.
I have a SqlDataReader, but it never enters into Read().
When I debug it, it pass the loop while(readerOne.Read()). It never enter into this loop even though there is data.
public static List<Pers_Synthese> Get_ListeSynthese_all(string codeClient, DateTime DateDeb, DateTime DateFin)
{
try
{
using (var connectionWrapper = new Connexion())
{
var connectedConnection = connectionWrapper.GetConnected();
string sql_Syntax = Outils.LoadFileToString(Path.Combine(appDir, #"SQL\Get_ListeSynthese_All.sql"));
SqlCommand comm_Command = new SqlCommand(sql_Syntax, connectionWrapper.conn);
comm_Command.Parameters.AddWithValue("#codeClioent", codeClient);
comm_Command.Parameters.AddWithValue("#DateDeb", DateDeb);
comm_Command.Parameters.AddWithValue("#DateFin", DateFin);
List<Pers_Synthese> oListSynthese = new List<Pers_Synthese>();
SqlDataReader readerOne = comm_Command.ExecuteReader();
while (readerOne.Read())
{
Pers_Synthese oSyntehse = new Pers_Synthese();
oSyntehse.CodeTrf = readerOne["CODE_TARIF"].ToString();
oSyntehse.NoLV = readerOne["NOID"].ToString();
oSyntehse.PrxUnitaire = readerOne["PRIX_UNITAIRE"].ToString();
oSyntehse.ZoneId = readerOne["LE_ZONE"].ToString();
oSyntehse.LeZone = readerOne["LIB_ZONE"].ToString();
oSyntehse.LeDept = readerOne["DEPT"].ToString();
oSyntehse.LeUnite = readerOne["ENLEV_UNITE"].ToString();
oSyntehse.LePoids = Convert.ToInt32(readerOne["POID"]);
//oSyntehse.LePoidsCorr = Convert.ToInt32(readerOne["POID_CORR"]);
oSyntehse.LeColis = readerOne["NBR_COLIS"].ToString();
oSyntehse.LeCr = readerOne["NBR_CREMB"].ToString();
oSyntehse.SumMontantCR = readerOne["ENLEV_CREMB"].ToString();
oSyntehse.LeVd = readerOne["NBR_DECL"].ToString();
oSyntehse.SumMontantVD = readerOne["ENLEV_DECL"].ToString();
oSyntehse.LePrixHT = readerOne["PRIX_HT"].ToString();
oSyntehse.LePrixTTC = readerOne["PRIX_TTC"].ToString();
oSyntehse.TrDeb = readerOne["TR_DEB"].ToString();
oSyntehse.TrFin = readerOne["TR_FIN"].ToString();
oListSynthese.Add(oSyntehse);
}
readerOne.Close();
readerOne.Dispose();
return oListSynthese;
}
}
catch (Exception excThrown)
{
throw new Exception(excThrown.Message);
}
}
When I debug it with SQL Server profiler it shows the data....that meant the data is not empty, but it never enter into this loop.
while (readerOne.Read())
{
by the way my connection class:
class Connexion : IDisposable
{
public SqlConnection conn;
public SqlConnection GetConnected()
{
try
{
string strConnectionString = Properties.Settings.Default.Soft8Exp_ClientConnStr;
conn = new SqlConnection(strConnectionString);
}
catch (Exception excThrown)
{
conn = null;
throw new Exception(excThrown.InnerException.Message, excThrown);
}
// Ouverture et restitution de la connexion en cours
if (conn.State == ConnectionState.Closed) conn.Open();
return conn;
}
public Boolean IsConnected
{
get { return (conn != null) && (conn.State != ConnectionState.Closed) && (conn.State != ConnectionState.Broken); }
}
public void CloseConnection()
{
// Libération de la connexion si elle existe
if (IsConnected)
{
conn.Close();
conn = null;
}
}
public void Dispose()
{
CloseConnection();
}
}
and my SQL Statement:
exec sp_executesql N'SELECT CODE_TARIF,PRIX_UNITAIRE,TR_DEB,TR_FIN,LE_ZONE,T_TARIF_ZONE.LIBELLE as LIB_ZONE,
SUBSTRING(CP_DEST,1,2) as DEPT,T_UNITE.LIBELLE as ENLEV_UNITE,
count(NOID)as NOID,
SUM(CASE WHEN POID_CORR IS NOT NULL THEN POID_CORR ELSE POID END) as POID,sum(NBR_COLIS)as NBR_COLIS,COUNT(NULLIF(ENLEV_CREMB,0))as NBR_CREMB, sum(ENLEV_CREMB)as ENLEV_CREMB,COUNT(NULLIF(ENLEV_DECL,0))as NBR_DECL,sum(ENLEV_DECL)as ENLEV_DECL,sum(PRIX_HT)as PRIX_HT,sum(PRIX_TTC)as PRIX_TTC, sum (POID_CORR)as POID_CORR
FROM LETTRE_VOIT_FINAL
LEFT JOIN T_TARIF_ZONE ON LETTRE_VOIT_FINAL.LE_ZONE = T_TARIF_ZONE.NO_ID
LEFT JOIN T_UNITE ON LETTRE_VOIT_FINAL.ENLEV_UNITE = T_UNITE.NO_ID
where code_client = #codeClioent
and DATE_CLOTUR_REEL BETWEEN #DateDeb AND #DateFin
and STATUT_LV = 2
group by CODE_TARIF,PRIX_UNITAIRE,TR_DEB,TR_FIN,LE_ZONE,T_TARIF_ZONE.LIBELLE,SUBSTRING(CP_DEST,1,2),T_UNITE.LIBELLE
order by LE_ZONE,PRIX_UNITAIRE
',N'#codeClioent nvarchar(8),#DateDeb datetime,#DateFin datetime',#codeClioent=N'17501613',#DateDeb='2013-06-05 00:00:00',#DateFin='2013-06-05 23:59:00'
it return the data on SQL profiler:
my real query :
SELECT CODE_TARIF,PRIX_UNITAIRE,TR_DEB,TR_FIN,LE_ZONE,T_TARIF_ZONE.LIBELLE as LIB_ZONE,
SUBSTRING(CP_DEST,1,2) as DEPT,T_UNITE.LIBELLE as ENLEV_UNITE,
count(NOID)as NOID,
SUM(CASE WHEN POID_CORR IS NOT NULL THEN POID_CORR ELSE POID END) as POID,sum(NBR_COLIS)as NBR_COLIS,COUNT(NULLIF(ENLEV_CREMB,0))as NBR_CREMB, sum(ENLEV_CREMB)as ENLEV_CREMB,COUNT(NULLIF(ENLEV_DECL,0))as NBR_DECL,sum(ENLEV_DECL)as ENLEV_DECL,sum(PRIX_HT)as PRIX_HT,sum(PRIX_TTC)as PRIX_TTC, sum (POID_CORR)as POID_CORR
FROM LETTRE_VOIT_FINAL
LEFT JOIN T_TARIF_ZONE ON LETTRE_VOIT_FINAL.LE_ZONE = T_TARIF_ZONE.NO_ID
LEFT JOIN T_UNITE ON LETTRE_VOIT_FINAL.ENLEV_UNITE = T_UNITE.NO_ID
where code_client = #codeClioent
and DATE_CLOTUR_REEL BETWEEN #DateDeb AND #DateFin
and STATUT_LV = 2
group by
CODE_TARIF,PRIX_UNITAIRE,TR_DEB,TR_FIN,LE_ZONE,T_TARIF_ZONE.LIBELLE,SUBSTRING(CP_DEST,1,2),T_UNITE.LIBELLE
order by LE_ZONE,PRIX_UNITAIRE
it is strange....when the data is between :
DATE_CLOTUR_REEL BETWEEN '2013-06-05 00:00:00' and '2013-06-05 23:59:00'
but
DATE_CLOTUR_REEL BETWEEN '2013-06-01 00:00:00' and '2013-06-05 23:59:00'
it works.
This is the way it should be. You are not doing the connection.Open()
Also set up the connection string.
private static void ReadOrderData(string connectionString)
{
string queryString =
"SELECT OrderID, CustomerID FROM dbo.Orders;";
using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlCommand command =
new SqlCommand(queryString, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
// Call Read before accessing data.
while (reader.Read())
{
ReadSingleRow((IDataRecord)reader);
}
// Call Close when done reading.
reader.Close();
}
}
The perfect example of how to do it belongs to MSDN - Microsoft Website
NOTICE:
SqlCommand command =
new SqlCommand(queryString, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
Create the SqlCommand
then open the connection
You are doing it the other way, you open it and then create the command.
I also don't see where you set the query string, I just see that you add the parameters; are you missing it?
This is perhaps not the answer you're looking for, but your code sample exhibits a number of bad coding practices that are easy to fall into due to ADO.NET's poor API design. Rather than manually do all this sql-to-.net conversion, you should use a library that does this for you.
It's easier to avoid bugs when you're not using a bug-prone API.
I recommend PetaPoco - it's easier to use than your current code, and has virtually no overhead (and given your example, is probably faster). There are many other alternatives, however.
Issues with your code sample:
Improperly disposed objects: you aren't disposing SqlCommand and SqlDataReader properly. You possibly aren't disposing connections either (but that depends on Connexion internals).
Using .ToString rather than type-safe casts. You should never extract data from an SqlDataReader like that because it undermines the whole point of the type system, and it's slow to boot. (PetaPoco or something similar will help a lot here)
You're discarding stack traces on error due to the (pointless) try-catch. That just makes your code less readable and harder to debug. Don't catch unless you have have.
Keeping your query away from the code - your code is tightly coupled to the query, and this separation just makes it hard to keep them in sync. Also, loading from the filesystem each and everytime you query is slow and opens up unnecessary filesystem-related failure modes such as locking, max path lengths, and permissions. This is probably the source of your bug - your query probably doesn't do what you think it does.
With PetaPoco or something similar, your entire function would look something like this:
public static List<Pers_Synthese> Get_ListeSynthese_all(
string codeClient, DateTime DateDeb, DateTime DateFin) {
var db = new PetaPoco.Database("Soft8Exp_ClientConnStr");
//you should probably not be storing a query in a file.
//To be clear: your query should not be wrapped in exec sp_executesql,
//ADO.NET will do that for you.
string sql_Syntax = Outils.LoadFileToString(
Path.Combine(appDir, #"SQL\Get_ListeSynthese_All.sql"));
//You'll need to rename Pers_Synthese's properties to match the db,
// or vice versa, or you can annotate the properties with the column names.
return db.Fetch<Pers_Synthese>(sql_Syntax, new {
codeClioent = codeClient, //I suspect this is a typo
DateDeb,
DateFin
});
}
And in that much shorter, readable, faster form, you'll hopefully find whatever bug you have much faster.
Alternatives:
PetaPoco
Dapper (fewer features, but stackoverflow uses it!)
OrmLite (of ServiceStack fame)
Massive (older, uses dynamic which is a feature that can cause bad habits - I don't recommend this unless you really know what you're doing)
You could use heavier, more invasive ORM's like the Entity framework and NHibernate, but these require quite a bit more learning, and they're much slower, and they impose a particular workflow on you which I don't think makes them the best choice in your case.
when i debug it with sql profiler it show the data....that meant the data is not empty, but it never enter into this loop.
It's the other way round: if it never enters into this loop, then it means "the data is empty", i.e. the query returns no rows.
The bug is in your code, not SqlReader: you possibly have the wrong values in your parameters, or maybe the query you read from a file isn't what you think it is. Get the debugger out and inspect the query text and parameters.
I have following code that uses SqlTransaction
string connectionString = ConfigurationManager.AppSettings["ConnectionString"];
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlTransaction transaction = connection.BeginTransaction();
int logID = HelperClass.InsertException(connection, 1, DateTime.Now, "Y", "test", "test", 1, 1, transaction);
LogSearch logSearch = new LogSearch();
logSearch.LogID = 258;
Collection<Log> logs = HelperClass.GetLogs(logSearch, connectionString);
}
This code is throwing the following exception.
Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
However if I pass a hard coded value for LogID, there is no exception.
QUESTION
Why does exception come when I pass logID (result from InsertException()) ?
Please explain why there is NO exception when I pass a hard coded value as LogID
Note: InsertException() uses a connection with SqlTransaction whereas GetLogs() uses a new connection without any transaction
UPDATED QUESTION
The Business Layer code does not use Transaction. I need to call the Business Layer methods in my Unit Testing code shown above (for integration testing). How can we apply transaction to UT code (for integration testing) even though the Business Layer does not use transaction? From #jbl answer, it seems like, it is not at all possible to use transaction in Unit Testing. How can we apply transaction for UT code.
CODE
public static class HelperClass
{
public static Collection<Log> GetLogs(LogSearch logSearch, string connectionString)
{
Collection<Log> logs = new Collection<Log>();
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
string commandText = "SELECT * FROM Application_EX WHERE application_ex_id = #application_ex_id";
using (SqlCommand command = new SqlCommand(commandText, connection))
{
command.CommandType = System.Data.CommandType.Text;
//Parameter value setting
command.Parameters.AddWithValue("#application_ex_id", logSearch.LogID);
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
}
}
}
}
}
return logs;
}
public static Int16 InsertException(SqlConnection connection, Int16 applicationID, DateTime createdDate, string isInternalLocationIndicator, string exceptionDescription, string operation, Int16 severityLevelNumber, Int16 exceptionTypeCode, SqlTransaction transaction)
{
Int16 newLogID = 0;
string commandText = #"INSERT INTO Application_Ex
VALUES (#severity_level_nbr, #appl_service_id, #ex_internal_appl_ind,
#application_ex_txt,#ex_location_txt,#create_ts,#application_ex_code);
SELECT SCOPE_IDENTITY() AS [LogIdentity];";
using (SqlCommand command = new SqlCommand(commandText, connection, transaction))
{
command.CommandType = System.Data.CommandType.Text;
command.Parameters.AddWithValue("#severity_level_nbr", severityLevelNumber);
command.Parameters.AddWithValue("#appl_service_id", applicationID);
command.Parameters.AddWithValue("#ex_internal_appl_ind", isInternalLocationIndicator);
command.Parameters.AddWithValue("#application_ex_txt", exceptionDescription);
command.Parameters.AddWithValue("#ex_location_txt", operation);
command.Parameters.AddWithValue("#create_ts", createdDate);
command.Parameters.AddWithValue("#application_ex_code", exceptionTypeCode);
newLogID = Convert.ToInt16(command.ExecuteScalar(), CultureInfo.InvariantCulture);
}
return newLogID;
}
}
QUESTION
Why does exception come when I pass logID (result from InsertException()) ?
Please explain why there is NO exception when I pass a hard coded value as LogID
ANSWERS
When a new record is inserted with using a transaction, that means the new record is not finally committed until you yourself commit the transaction. Until that time the new record is LOCKED, which means that any query that touches that new record will be suspended until a timeout occurs. In your case, the call to the method GetLogs is executed while the transaction is still running, and that method searches for the newly inserted record-id. Since the row is locked, your GetLogs call will wait until the timeout occurs, which results in a timeout exception.
In the case of a hard coded value, the call to GetLogs will search for the existing record with the corresponding id. Since you are searching for a PK-value, SQL does not have to search all rows, since it is the PK. Therefore, the existing row is found, and returned, all in a separate transaction, since the transactions are not overlapping in the data they touch.
Suppose your method GetLogs was searching the table on another column, a non-pk column (say for example application_message), then the whole table would have to be read to find a row (or rows) with the corresponding value for application_message. This would result in a query that always touches the newly inserted locked row, and then also with a hard coded (application_message) value your would get a timeout exception. This I added just to clarify the locking, and when SQL does or does not need to touch the locked row(s).
Hope this helps.
I guess that's because HelperClass.GetLogs(logSearch, connectionString); instantiate a new connection out of scope of your transaction :
You may, at your will :
have your helper class accept the connection object holding the transaction instead of a connection string
or replace "SELECT * FROM Application_EX WHERE application_ex_id = #application_ex_id" with "SELECT * FROM Application_EX with (nolock) WHERE application_ex_id = #application_ex_id"
note that the second case would sometimes return incorrect values, and would not return values you are currently inserting in your transaction
Hope this will help
I often create SqlCommands using the below pattern for single threaded applications.
I am now creating a webservice, and I am concerned that this pattern will not hold up to handling requests from multiple client at the same time.
Is there a way to use a single "prepared" SqlCommand for multiple clients other than simply locking the function to only allow a single client to run at once?
private static SqlCommand cmdInsertRecord;
public static void InsertRecord(String parameter1, String parameter2, SqlConnection connection, SqlTransaction transaction)
{
if (cmdInsertRecord == null)
{
//Create command
cmdInsertRecord = connection.CreateCommand();
cmdInsertRecord.CommandText = #"SQL QUERY";
//Add parameters to command
cmdInsertRecord.Parameters.Add("#Parameter1", SqlDbType.Int);
cmdInsertRecord.Parameters.Add("#Parameter2", SqlDbType.DateTime);
//Prepare the command for use
cmdInsertRecord.Prepare();
}
cmdInsertRecord.Transaction = transaction;
//Note SetParameter is an extension that handles null -> DBNull.
cmdInsertRecord.SetParameter("#Parameter1", parameter1);
cmdInsertRecord.SetParameter("#Parameter2", parameter2);
cmdInsertRecord.ExecuteNonQuery();
}
Is there a way to use a single "prepared" SqlCommand for multiple clients other than simply locking the function to only allow a single client to run at once?
You shouldn't - why would you want to?
You should create a new SqlConnection each time, and a new SqlCommand, and use that. Let the connection pool and (presumably) statement pool handle making it efficient.
Having a static SqlConnection or SqlCommand is just asking for trouble, IMO.
This is interesting (to me anyway), and I'd like to see if anyone has a good answer and explanation for this behavior.
Say you have a singleton database object (or static database object), and you have it stored in a class Foo.
public class Foo
{
public static SqlConnection DBConn = new SqlConnection(ConfigurationManager.ConnectionStrings["BAR"].ConnectionString);
}
Then, lets say that you are cognizant of the usefulness of calling and disposing your connection (pretend for this example that its a one-time use for purposes of illustration). So you decide to use a 'using' block to take care of the Dispose() call.
using (SqlConnection conn = Foo.DBConn)
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "SP_YOUR_PROC";
cmd.ExecuteNonQuery();
}
conn.Close();
}
This fails, throwing an exception on the call to open the connection, stating that the "ConnectionString property is not initialized". It's not an issue with pulling the connection string from the app.config/web.config. When you investigate in a debug session you see that Foo.DBConn is not null, but contains empty properties.
Why is this?
A little out of topic and not really answering your question but why using a singleton for SqlConnection when ADO.NET already uses a connection pool? Your code could have very well looked like this:
using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["BAR"].ConnectionString))
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.Connection = conn;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "SP_YOUR_PROC";
cmd.ExecuteNonQuery();
}
And one less thing to worry about in your program: connection lifecycle
Perhaps you do not have the corresponding connectionStrings node in you web/app.config?
<connectionStrings>
<add name="BAR"
connectionString="Data Source=localhost\sqlexpress;Initial Catalog=mydatabase;User Id=myuser;Password=mypassword;" />
The static field is evaluated sometime before it is used (not deterministically). See beforefieldinit for more detail. So the system may not be ready for creating an SQL-connection when it is called or maybe even properly create the static field after you use it.
Additionally, how would you handle a second SQL-command after you closed the first one? I don't know exactly how SqlConnection works, but after closing (note that this cals Dispose) and disposing the connection, your static Foo.DBConn should be gone, i.e. it won't be reevaluated.
If you want to keep your basic infrastructure, I would replace the static field with a static property which returns a new SqlConnection on get:
public static SqlConnection DBConn
{
get
{
return new SqlConnection(ConfigurationManager.ConnectionStrings["BAR"].ConnectionString);
}
}