I am Creating WinForm application using C# and SqlServer. I have to handle many database CRUD Queries on it. And also there are so many forms and so many controllers.
Now I want to know is, If i create common class for handle database connectivity with many methods for open connection, close connection, execute Sql command or do any other data retrievals. This method is good or bad?
or below method for run every query is good or bad?
using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Initial Catalog=MYDB"))
{
connection.Open();
// Pool A is created.
}
which method is better for performance and security?
Here are some points to think about when using a connection.
1) Dispose the connection object as soon as you no longer need it by using the using statement:
using (var conn = new SqlConnection(connectionstring))
{
// your sql magic goes here
}
2) If you're not disposing the object immediately, you can make sure the connection is closed using a try-finally statement:
var conn = new SqlConnection(connectionstring);
try
{
// do sql shizzle
}
finally
{
conn.Close();
}
3) To prevent SQL injection, use parameterized queries, never concatenated strings
using (var conn = new SqlConnection(connectionstring))
{
conn.Open();
using(var comm = new SqlCommand("select * from FooBar where foo = #foo", conn))
{
comm.Parameters.Add(new SqlParameter("#foo", "bar"));
// also possible:
// comm.Parameters.AddWithValue("#foo", "bar");
using(var reader = comm.ExecuteReader())
{
// Do stuff with the reader;
}
}
}
4) If you're performing multiple update, insert or delete statements, and they all need to be succesful at once, use a transaction:
using (var conn = new SqlConnection(connectionstring))
{
conn.Open();
using(var trans = conn.BeginTransaction())
{
try
{
using(var comm = new SqlCommand("delete from FooBar where fooId = #foo", conn, trans))
{
comm.Parameters.Add(new SqlParameter { ParameterName = "#foo", DbType = System.Data.DbType.Int32 });
for(int i = 0; i < 10 ; i++)
{
comm.Parameters["#foo"].Value = i;
comm.ExecuteNonQuery();
}
}
trans.Commit();
}
catch (Exception exe)
{
trans.Rollback();
// do some logging
}
}
}
5) Stored procedures are used similarly:
using (var conn = new SqlConnection(connectionstring))
{
conn.Open();
using (var comm = new SqlCommand("FooBarProcedure", conn) { CommandType = CommandType.StoredProcedure })
{
comm.Parameters.Add(new SqlParameter("#FooBar", "shizzle"));
comm.ExecuteNonQuery();
}
}
(Source stored procedures: this Answer)
Multi threading: The safest way to use multi threading and SQL connections is to always close and dispose your connection object. It's the behavior the SqlConnection was designed for. (Source: Answer John Skeet)
Best practice is make a common DBHelper class and create CRUD methods into that class.
I am adding code snippet.This may help you.
web.config
<connectionStrings>
<add name="mssqltips"
connectionString="data source=localhost;initial catalog=mssqltips;Integrated Security=SSPI;"
providerName="System.Data.SqlClient" />
</connectionStrings>
DBHelper.cs
//Opening Connection
public SqlConnection GetConnection(string connectionName)
{
string cnstr = ConfigurationManager.ConnectionStrings[connectionName].ConnectionString;
SqlConnection cn = new SqlConnection(cnstr);
cn.Open();
return cn;
}
//for select
public DataSet ExecuteQuery(
string connectionName,
string storedProcName,
Dictionary<string, sqlparameter=""> procParameters
)
{
DataSet ds = new DataSet();
using(SqlConnection cn = GetConnection(connectionName))
{
using(SqlCommand cmd = cn.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = storedProcName;
// assign parameters passed in to the command
foreach (var procParameter in procParameters)
{
cmd.Parameters.Add(procParameter.Value);
}
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(ds);
}
}
}
return ds;
}
//for insert,update,delete
public int ExecuteCommand(
string connectionName,
string storedProcName,
Dictionary<string, SqlParameter> procParameters
)
{
int rc;
using (SqlConnection cn = GetConnection(connectionName))
{
// create a SQL command to execute the stored procedure
using (SqlCommand cmd = cn.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = storedProcName;
// assign parameters passed in to the command
foreach (var procParameter in procParameters)
{
cmd.Parameters.Add(procParameter.Value);
}
rc = cmd.ExecuteNonQuery();
}
}
return rc;
}
If you do not want to dispose context every time you can create repository class and inject SqlConnection inside.
using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Initial Catalog=MYDB"))
{
repository.SetConnection(connection);
var values = repository.GetSomething();
}
And Create Class:
public Class Repository
{
private SqlConnection _connection {get; set;}
public void SetConnection(SetConnection connection)
{
_connection = connection;
}
public string GetSomething()
{
_connection.Open();
//do stuff with _connection
_connection.Close();
}
}
Anyway I recommend you to read about ORM's (Entity Framework or Dapper) and SQL injection attack.
Related
Although this question seems like have answers already but my case is different, here's how.
It works the first time but fails for subsequent requests.
I'm creating the connection in the main class and passing to the DB class as a dependency in it's constructor and it's meant to be re-used for each call.
public class DB
{
private SqlConnection conn;
public DB(SqlConnection conn)
{
this.conn = conn;
}
public List<Records> GetRecords()
{
using (conn){
conn.Open();
using (SqlCommand cmd = new SqlCommand("SELECT * FROM Records", conn))
using (SqlDataReader reader = cmd.ExecuteReader())
{
List<Records> rows = new List<Records>();
while (reader.Read())
{
rows.Add(new Records(reader.GetString(1)));
}
return rows;
}
}
}
}
Caller class
string connection = $#"
Data Source=;
Initial Catalog=;
Persist Security Info=True;
User ID={env["DATABASE_USER"]};
Password={env["DATABASE_PASSWORD"]};";
Db db = new DB(new SqlConnection(connection));
db.GetRecords();
fail:
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request. System.InvalidOperationException: The ConnectionString property has
not been initialized.
I'm not 100% sure, but I guess the problem is the
using(conn)
when the using is closed, the SqlConnection will be disposed.
so when you call again db.GetRecords();,
conn.Open() is not initialized. -> exception
You shouldn't use SQLConnection as a field, but as a local variable inside the method. Change your class to take in the connection string inside it's parameter instead of an instance of SqlConnection and initialize it in any method that use it:
public class DB
{
private string connectionString;
public DB(string connectionString)
{
this.connectionString = connectionString;
}
public List<Records> GetRecords()
{
using (var conn = new SqlConnection(connectionString)){
conn.Open();
using (SqlCommand cmd = new SqlCommand("SELECT * FROM Records", conn))
using (SqlDataReader reader = cmd.ExecuteReader())
{
List<Records> rows = new List<Records>();
while (reader.Read())
{
rows.Add(new Records(reader.GetString(1)));
}
return rows;
}
}
}
}
For more details, read this.
I've always used Oledb Connection.
but now I need to connect with my Database via Sql connection
yet I don't know how to do so,
can some one provide me an example of a database connected with sql connection?
this code needs a sql connection to be done successfully.
protected void Button1_Click(object sender, EventArgs e)
{
string st = this.TextBox1.Text;
string sqlstr2 = "select * from hsinfo WHERE rname='"+st+ "'";
SqlCommand cmd = new SqlCommand(sqlstr2,);
using (SqlDataReader rd = cmd.ExecuteReader())
{
this.Label1.Text = rd["rmail"].ToString();
}
}
You can check the official Microsoft page for more details SqlConnection Class, but I will reproduce the given example below ...
Aditionally you can check also the Connection String Syntax linked in the previous link.
private static void CreateCommand(string queryString,
string connectionString)
{
using (SqlConnection connection = new SqlConnection(
connectionString))
{
SqlCommand command = new SqlCommand(queryString, connection);
command.Connection.Open();
command.ExecuteNonQuery();
}
}
This is a simple example code and it's working. This might help you.
Here NextMonth,NextYear,ProcessedDate are auto calculated values comes from another function don't think about that.
String cs = #"Data Source=LENOVO-G510;Initial Catalog=Nelna2;Persist Security Info=True;User ID=sa;Password=123";
protected void Save_Click(object sender, EventArgs e)
{
// SqlConnection con = new SqlConnection(cs);
using (SqlConnection con = new SqlConnection(cs))
{
try
{
SqlCommand command5 = new SqlCommand("insert into MonthEnd (month,year,ProcessedDate) values (#month2,#year2,#ProcessedDate2) ", con);
command5.Parameters.AddWithValue("#month2", NextMonth);
command5.Parameters.AddWithValue("#year2", NextYear);
command5.Parameters.AddWithValue("#ProcessedDate2", ProcessedDate);
command5.ExecuteNonQuery();
}
catch (SqlException ex)
{
Response.Write(ex.Message);
}
}
}
Connection string can be found in DB properties. right click on DB -> properties and Get the Connection String
There is no enougth information to build connection for you, but in the shortes you sth like this:
Server=...;Database=...;User ID=...;Password=...;
For more information just check ConnectionStrings website.
try below code and for more information about c# SQL server connection see this SQL Server Connection
string connetionString = null;
SqlConnection cnn ;
connetionString = "Data Source=ServerName;Initial Catalog=DatabaseName;User ID=UserName;Password=Password"
cnn = new SqlConnection(connetionString);
try
{
cnn.Open();
MessageBox.Show ("Connection Open ! ");
cnn.Close();
}
catch (Exception ex)
{
MessageBox.Show("Can not open connection ! ");
}
I would do something like this:
public static List<Test> GetTests(string testVariable)
{
DataTable result = new DataTable();
using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["Database"].ConnectionString))
{
connection.Open();
GetQuery(
connection,
QueryGetTests,
ref result,
new List<SqlParameter>()
{
new SqlParameter("#testVariable", testVariable)
}
);
return result.Rows.OfType<DataRow>().Select(DataRowToTest).ToList();
}
}
private static void GetQuery(SqlConnection connection, string query, ref DataTable dataTable, List<SqlParameter> parameters = null)
{
dataTable = new DataTable();
using (SqlCommand command = new SqlCommand(query, connection))
{
command.CommandTimeout = 120;
if (parameters != null)
{
foreach (SqlParameter parameter in parameters)
{
command.Parameters.Add(parameter);
}
}
using (SqlDataAdapter reader = new SqlDataAdapter(command))
{
reader.Fill(dataTable);
}
}
}
I think this can help you.
string sqlString = "select * from hsinfo WHERE rname=#st";
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["DatabaseName"].ConnectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(sqlString, conn))
{
cmd.Parameters.Add("#st", st);
using (SqlDataReader rd = cmd.ExecuteReader())
{
if (rd.Read())
{
this.Label1.Text = rd["rmail"].ToString();
}
}
}
}
Trick:
Create a file with .udl Extension on your Desktop
Run it by Double click
Compile form by Choosing provider, username, password, etc...
Test connection and save
Close the form
Open now the .udl file with Notepad
You will see the connection string that you can use with ADO.NET
I'm trying to execute SQL query from C# code, but it doesn't return any values, but when I write query directly to SQL it works fine.
static int TestGettingData()
{
int rows;
string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
using (SqlConnection connection = new SqlConnection(connectionString))
{
using (SqlCommand command = new SqlCommand("SELECT [UserName] FROM [aspnet_Users]", connection))
{
connection.Open();
rows = command.ExecuteNonQuery();
}
}
return rows;
}
And connection string:
<connectionStrings>
<add name ="DefaultConnection" connectionString="Server=myServer;
Database=MyDatabase;User
Id=User;
Password=password;
MultipleActiveResultSets=True;"
providerName="System.Data.SqlClient"/>
</connectionStrings>
And TestGettingData() always returning -1.
There are multiple ways to get Data out of your Database
ExecuteScalar
if you have one result field you can use
string Command = "SELECT [UserName] FROM [aspnet_Users];";
using (SqlConnection myConnection = new SqlConnection(ConnectionString))
{
myConnection.Open();
using (SqlCommand myCommand = new SqlCommand(Command, myConnection))
{
string Result = (string)myCommand.ExecuteScalar(); // returns the first column of the first row
}
}
SqlDataAdapter
if you expect multiple rows / columns you can load it into a DataTable by
string Command = "SELECT [UserName] FROM [aspnet_Users]";
using (SqlConnection myConnection = new SqlConnection(ConnectionString))
{
using (SqlDataAdapter myDataAdapter = new SqlDataAdapter(Command, myConnection))
{
DataTable dtResult = new DataTable();
myDataAdapter.Fill(dtResult);
}
}
SqlDataReader
Another solution is the SQLDataReader. E.g. if you want to load all rows of a column into a List. This has less overhead than the DataAdapter.
List<string> Result = new List<string>();
string Command = "SELECT [UserName] FROM [aspnet_Users];";
using (SqlConnection mConnection = new SqlConnection(ConnectionString))
{
mConnection.Open();
using (SqlCommand cmd = new SqlCommand(Command, mConnection))
{
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Result.Add((string)reader[0]);
}
}
}
}
Because documentation say so;
For UPDATE, INSERT, and DELETE statements, the return value is the
number of rows affected by the command. When a trigger exists on a
table being inserted or updated, the return value includes the number
of rows affected by both the insert or update operation and the number
of rows affected by the trigger or triggers. For all other types of
statements, the return value is -1. If a rollback occurs, the return
value is also -1.
You can use COUNT(*) wiht ExecuteScalar method to get your number.
using (SqlCommand command = new SqlCommand("SELECT COUNT([UserName]) FROM [aspnet_Users]", connection))
{
connection.Open();
rows = (int)command.ExecuteScalar();
}
Here's what I think is the best way to access and retrieve data from a DB:
Create a DBconnection class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SQLexample
{
/// <summary>
/// A class that makes the connection to the SQL Database
/// </summary>
class DBconnection
{
// variables
private string sql_string;
private string strCon;
System.Data.SqlClient.SqlDataAdapter da_1;
// set methods
public string Sql
{
set { sql_string = value; }
}
public string connection_string
{
set { strCon = value; }
}
// DataSet
public System.Data.DataSet GetConnection
{
get { return MyDataSet(); }
}
// MyDataSet method
private System.Data.DataSet MyDataSet()
{
System.Data.SqlClient.SqlConnection con = new System.Data.SqlClient.SqlConnection(strCon);
con.Open();
da_1 = new System.Data.SqlClient.SqlDataAdapter(sql_string, con);
System.Data.DataSet dat_set = new System.Data.DataSet();
da_1.Fill(dat_set, "Table_Data_1");
con.Close();
return dat_set;
}
// Update DB method
public void UpdateDB(System.Data.DataSet ds)
{
System.Data.SqlClient.SqlCommandBuilder cb = new System.Data.SqlClient.SqlCommandBuilder(da_1);
cb.DataAdapter.Update(ds.Tables[0]);
}
}
}
Then, from your other classes, whenever you want to access DB you do it like this:
try
{
objConnect = new DBconnection();
conStringAUTH = Properties.Settings.Default.authConnectionString;
objConnect.connection_string = conStringAUTH;
objConnect.Sql = "QUERY GOES HERE";
ds = objConnect.GetConnection;
// Data manipulation
maxRows = ds.Tables[0].Rows.Count;
if (maxRows == 0)
{
// Your query returned no values
}
}
catch (Exception err)
{
MessageBox.Show(err.Message);
}
Hope this helps...
I call ExecuteReader(); to get data, then i need to get another data with another query. My structure's been always like this :
class SomeClass
{
public static void Main(String[] args)
{
SqlConnection sqlConn = new SqlConnection();
sqlConn.ConnectionString = "some connection string"
SqlCommand SQLCmd = new SqlCommand();
SQLCmd.CommandText = "some query";
SQLCmd.Connection = sqlConn;
sqlConn.Open();
sqlReader = SQLCmd.ExecuteReader();
while (sqlReader.Read())
{
//some stuff here
}
sqlReader.Dispose();
sqlReader.Close();
sqlConn.Close();
SQLCmd.CommandText = "another query";
sqlConn.Open();
sqlReader = SQLCmd.ExecuteReader();
while (sqlReader.Read())
{
//some other stuff here
}
sqlReader.Dispose();
sqlReader.Close();
sqlConn.Close();
}
}
They share the same connection string. What else can they share ? Can they share same sqlConn.Open(); ? What is the proper way of resource allocating and avoiding errors ?
BTW it works as it is. Thanks in advance.
This is how I would write all of that:
class SomeClass
{
public static void Main(String[] args)
{
using (SqlConnection sqlConn = new SqlConnection("some connection string"))
{
sqlConn.Open();
using (SqlCommand comm = new SqlCommand("some query", conn))
using (var sqlReader = comm.ExecuteReader())
{
while (sqlReader.Read())
{
//some stuff here
}
}
using (SqlCommand comm = new SqlCommand("some query", conn))
using (var sqlReader = comm.ExecuteReader())
{
while (sqlReader.Read())
{
//some other stuff here
}
}
}
}
}
The using statement handles disposing of items when the block is finished. As for sharing stuff, you could leave the connection open across the commands.
The most important thing to dispose out of all of that would be the connection, but I tend towards honouring a using statement if an item is IDisposable regardless of what it actually does in the background (which is liable to change as it's an implementation detail).
Don't forget, there is also Multiple Active Result Sets (as demonstrated in this answer) from a single command and a single reader, where you advance the reader onto the next result set.
My slightly more flippant answer to how I might write all of that would be:
return connection.Query<T>(procedureName, param, commandType: CommandType.StoredProcedure);
Using Dapper ;-)
As alluded to in my comment - if possible, combine the two queries into one and then (if it still produces multiple result sets), use NextResult to move on.
Stealing Adam's structure, but with that change:
class SomeClass
{
public static void Main(String[] args)
{
using (SqlConnection sqlConn = new SqlConnection("some connection string"))
{
sqlConn.Open();
using (SqlCommand comm = new SqlCommand("some query; some other query;", conn))
using (var sqlReader = comm.ExecuteReader())
{
while (sqlReader.Read())
{
//some stuff here
}
if(sqlReader.NextResult())
{
while (sqlReader.Read())
{
//some other stuff here
}
}
}
}
}
}
The proper way is to wrap SqlConnections and SqlCommands in using-statements. This will force Dispose to be invoked on the objects when the using block is left, even if an Exception is thrown. (This is not the case with your current code.)
Something in the line of
using(var cnn = new SqlConnection("connectionstring")){
cnn.Open();
using(var cmd = new SqlCommand("SELECT 1")){
var reader = cmd.ExecuteReader();
while(reader.Read()) { /* doStuff */ }
}
}
Regardless of the approach Close/Dispose will not actually close the connection since connection setup is very expensive. It will just return the connection to a connection pool and allow other commands/readers to use it.
To manage resource you can use using like as shown under
...
SQLCmd.CommandText = "some query";
SQLCmd.Connection = sqlConn;
sqlConn.Open();
//using will dispose reader automatically.
using(sqlReader = SQLCmd.ExecuteReader())
{
while (sqlReader.Read())
{
//some stuff here
}
}
//sqlReader.Dispose();
//sqlReader.Close();
//sqlConn.Close();
SQLCmd.CommandText = "another query";
//no need to open connection again.
// sqlConn.Open();
// sqlReader = SQLCmd.ExecuteReader();
using(sqlReader = SQLCmd.ExecuteReader())
{
while (sqlReader.Read())
{
//some stuff here
}
}
//sqlReader.Dispose();
//sqlReader.Close();
//sqlConn.Close();
you can use using only for those classes which have implemented IDispose interface.
in your example you can use SqlConnection and SqlCommand also with using code block.
Use 'using', you don't need to manually close and dispose.
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = new SqlCommand("spTest", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("#employeeid", employeeID));
command.CommandTimeout = 5;
command.ExecuteNonQuery();
}
Open a new connection every time you need it is a best practices. ADO.net use connection pool to menage connection.
http://msdn.microsoft.com/it-it/library/8xx3tyca(v=vs.110).aspx
Dont forget your try catch statements though :)
class SomeClass
{
public static void Main(String[] args)
{
using (SqlConnection sqlConn = new SqlConnection("some connection string"))
{
try{
sqlConn.Open();
using (SqlCommand comm = new SqlCommand("some query", conn))
using (var sqlReader = comm.ExecuteReader())
{
while (sqlReader.Read())
{
//some stuff here
}
}
using (SqlCommand comm = new SqlCommand("some query", conn))
using (var sqlReader = comm.ExecuteReader())
{
while (sqlReader.Read())
{
//some other stuff here
}
}
}
catch()
{
// Do exception catching here or rollbacktransaction if your using begin transact
}
finally
{
sqlConn.Close();
}
}
}
}
I am trying to get a better handle on decoupling my code, code reuse, etc.
I'm tired of typing the below every time I want to read some rows:
using(SqlConnection conn = new SqlConnection(myConnString))
{
using(SqlCommand cmd = new SqlCommand(cmdTxt, conn))
{
conn.Open();
using(SqlDataReader rdr = cmd.ExecuteReader())
{
while(rdr.Read())
{
/* do something with rows */
}
}
}
}
I understand there is LINQ to SQL (I don't like it), and the Entity Framework (still a baby). I have no problems having to type my queries out, I just don't want to have to type the command contruction, row iterator, etc each time.
I looked around and found something that I thought would work for me, and tried to implement it to make things easier for me. As you can see in the comment, I get an error that the SqlDataReader is closed. I'm guessing it's probably because of the using statement int the DataFactory.ExecuteReader() method. When the reader is returned, the dispose method is called on my SqlConnection and SqlCommand variables. Am I right there? If so, how should one manage the connection and command variables?
Edit: I updated my code example to better reflect what I am doing.
public class DataFactory
{
public DataFactory()
{}
public DataFactory(string connectionString)
{
_connectionString = connectionString;
}
protected _connectionString = "Data Source=Localhost, etc, etc";
private string ConnectionString
{
get{return _connectionString;}
}
public SqlConnection GetSqlConnection()
{
return new SqlConnection(ConnectionString);
}
public SqlDataReader ExecuteReader(string cmdTxt)
{
using(SqlConnection conn = new SqlConnection(ConnectionString))
{
using(SqlCommand cmd = new SqlCommand(cmdTxt, conn))
{
conn.Open();
return cmd.ExecuteReader();
}
}
}
}
public IRepository<T>
{
T GetById(int id);
}
public MyTypeRepository: IRepository<MyType>
{
private static DataFactory _df = new DataFactory();
public MyType GetById(int id)
{
string cmdTxt = String.Format("SELECT Name FROM MyTable WHERE ID = {0}", id);
using(SqlDataReader rdr = _df.ExecuteReader(cmdTxt))
{
if(rdr.Read()) /* I get an error that the reader is already closed here */
{
return new MyType(
Convert.ToInt32(rdr["Id"]),
rdr["Name"]);
}
else
{
return null;
}
}
}
}
public class MyType
{
public MyType(int id, string name)
{
_id = id;
_name = name;
}
private string _name;
public string Name
{
get{return _name;}
}
private int _id;
public int Id
{
get{return _id;}
}
public override void ToString()
{
return string.Format("Name: {0}, Id: {1}", Name, Id);
}
}
public class Program
{
private static MyTypeRepository _mtRepo = new MyTypeRepository();
static void Main()
{
MyType myType = _mtRepo.GetById(1);
Console.WriteLine(myType.ToString());
}
}
I also would like to know if what I'm doing makes any sense, or, if not, how to achieve something similar so that I don't have to type the connection creation, etc so often.
Your method ExecuteReader will close the connection before returning the Reader. Instead it should be implemented something like:
public IDataReader ExecuteReader(string cmdTxt)
{
SqlConnection conn = new SqlConnection(...);
try
{
SqlCommand cmd = new SqlCommand(cmdTxt, conn);
conn.Open();
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
catch
{
conn.Close();
throw;
}
}
Callers of the ExecuteReader method will need to dispose the IDataReader:
using(IDataReader reader = ExecuteReader(commandText))
{
...
} // reader will be disposed here and will close the connection.
Note that the above does not call Dispose on the SqlCommand object. In my experience and from looking at SqlCommand with Reflector it's not necessary as long as the SqlConnection is disposed. But I believe the following will work if you do want to dispose it:
public IDataReader ExecuteReader(string cmdTxt)
{
SqlConnection conn = new SqlConnection(...);
SqlCommand cmd = null;
try
{
cmd = new SqlCommand(cmdTxt, conn);
conn.Open();
IDataReader reader =
cmd.ExecuteReader(CommandBehavior.CloseConnection);
cmd.Dispose();
return reader;
}
catch
{
if (cmd != null) cmd.Dispose();
conn.Close();
throw;
}
}
It's very important that you close and/or dispose your data reader after using it then everyone who wants to use your DataFactory should remember to do that.I think it's a good idea to return a DataTable instead of SqlDataReader so that your DataFactory is not dependent to SqlDataReader.
I mean :
public DataTable ExecuteReader(string cmdTxt)
{
using(SqlConnection conn = new SqlConnection(ConnectionString))
{
using(SqlCommand cmd = new SqlCommand(cmdTxt, conn))
{
conn.Open();
using(SqlDataReader reader=cmd.ExecuteReader())
{
DataTable dt=new DataTable();
dt.Load(reader);
return dt;
}
}
}
}
EDIT:
Good point.I don't like data tables either ( We use NHibernate so I actually don't use data tables in our applications)
So if you'd like to map a data reader to your own objects maybe you can have a data mapper that maps data reader to your own objects I mean:
public T[] ExecuteReader<T>(string cmdTxt)
{
using(SqlConnection conn = new SqlConnection(ConnectionString))
{
using(SqlCommand cmd = new SqlCommand(cmdTxt, conn))
{
conn.Open();
using(SqlDataReader reader=cmd.ExecuteReader())
{
var result=new List<T>();
while(reader.Read())
result.Add(ObjectMapper.MapReader<T>(reader));
return result.ToArray();
}
}
}
}
What I do is I create an XML file with my queries and use an XSLT transformation to generate my DAL code CS files. You can go as fancy as you like, declare parameters in the XML and generate methods with appropriate signatures in the XSLT etc etc. I have a blog entry that covers, for a related topic, how to integrate the XSLT transformation into your Visual Studio project. Now one may argue that using a typed dataset is the same thing and is a free lunch, but in my case I use asynchronous DAL based on BeginExecute/EndExecute. None of the VS tools gets this approach right so I basically had to build my own.
I would say it's not really decoupling enough - basically any module you have with "using System.Data.SqlClient" is coupled to your database. The whole point of a DAL is that the application is coupled to the DAL and the DAL is coupled to the database.