Lately I find myself writing data access layer select methods where the code all takes this general form:
public static DataTable GetSomeData( ... arguments)
One pattern I've enjoyed looks like this as far as client code goes:
DataTable data = null;
using (StoredProcedure proc = new StoredProcedure("MyProcName","[Connection]"))
{
proc.AddParameter("@LoginName", loginName);
data = proc.ExecuteDataTable();
}
I usually make connection optional, and I will code in a way to pull it from ConnectionStrings config section or treat it as the actual connection string. This lets me reuse the dal in one off scenario's and is in part a habbit from the COM+ days when I stored the connection string using the object construction property.
I like this because it's easy to read and hides all the ADO code from me.
The only thing I do differently is I switched from my own internal database helper methods to the actual Data Access Application Block http://msdn.microsoft.com/en-us/library/cc309504.aspx
Makes it a little more standardized/uniform for other developers who know the enterprise library to ramp up on the code.
First, I think you already considered using an ORM vs. rolling your own. I won't go into this one.
My thoughts on rolling your own data access code:
My suggestion (I tried both methods - the suggestion is the latest working approach I came up with - it sort of evolved over time).
The aim is to end up with usage such as:
List<MyObject> objects = MyObject.FindMyObject(string someParam);
The benefit for me was that I only have to change one file in order to cope with changes in the database column names, types etc. (small changes in general). With some well thought regions, you can organize the code so that they're separate "layers" in the same object :). The other benefit is that the base class is really reusable from one project to another. And the code bloat is minimal (well, compared with the benefits. You could also fill datasets and bind them to UI controls :D
The limitations - you end up with one class per domain object (usually per main database table). And you can't load objects in existing transactions (although you could think of passing on the transaction, if you have one).
Let me know if you're interested in more details - I could expand the answer a bit.
There are so many ways to implement the DBAL, in my opinion you are on the right path. Somethings to consider in your implementation:
Add parameters using DbUtil.AddParameter(cmd, "@Id", SqlDbType.UniqueIdentifier, Id);
internal class DbUtil {
internal static SqlParameter CreateSqlParameter(
string parameterName,
SqlDbType dbType,
ParameterDirection direction,
object value
) {
SqlParameter parameter = new SqlParameter(parameterName, dbType);
if (value == null) {
value = DBNull.Value;
}
parameter.Value = value;
parameter.Direction = direction;
return parameter;
}
internal static SqlParameter AddParameter(
SqlCommand sqlCommand,
string parameterName,
SqlDbType dbType
) {
return AddParameter(sqlCommand, parameterName, dbType, null);
}
internal static SqlParameter AddParameter(
SqlCommand sqlCommand,
string parameterName,
SqlDbType dbType,
object value
) {
return AddParameter(sqlCommand, parameterName, dbType, ParameterDirection.Input, value);
}
internal static SqlParameter AddParameter(
SqlCommand sqlCommand,
string parameterName,
SqlDbType dbType,
ParameterDirection direction,
object value
) {
SqlParameter parameter = CreateSqlParameter(parameterName, dbType, direction, value);
sqlCommand.Parameters.Add(parameter);
return parameter;
}
}
Had to add my own:
Return DataReader from DataLayer in Using statement
The new pattern enables me to only have one record in memory at a time, but still encases the connection in a nice 'using' statement:
public IEnumerable<T> GetSomeData(string filter, Func<IDataRecord, T> factory)
{
string sql = "SELECT * FROM [SomeTable] WHERE SomeColumn= @Filter";
using (SqlConnection cn = new SqlConnection(GetConnectionString()))
using (SqlCommand cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.Add("@Filter", SqlDbType.NVarChar, 255).Value = filter;
cn.Open();
using (IDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
yield return factory(rdr);
}
rdr.Close();
}
}
}
The simplest Solution :
var dt=new DataTable();
dt.Load(myDataReader);
list<DataRow> dr=dt.AsEnumerable().ToList();