I'm trying to create a Data Framework in the form of a .dll so that I can reference it when creating new projects, as opposed to reinventing the wheel with each project I create.
I have an app.config in which I store my SQL connections, a class that uses the app.config to build my SQL ConnectionString (ConnectionStrings.cs) and a Logic class (Logic.cs) that'll build whatever objects I require from the SQL Server.
Here's the classes in the .dll:
ConnectionStrings.cs:
using System.Configuration;
using System.Data.SqlClient;
namespace DataFramework
{
public static class ConnectionStrings
{
static string _liveConnectionString = ConfigurationManager.ConnectionStrings["LiveDataSource"].ConnectionString;
static string _liveMISConnectionString = ConfigurationManager.ConnectionStrings["LiveDataSource_MIS"].ConnectionString;
static string _devConnectionString = ConfigurationManager.ConnectionStrings["DevDataSource"].ConnectionString;
static string _devMISConnectionString = ConfigurationManager.ConnectionStrings["DevDataSource_MIS"].ConnectionString;
public static SqlConnection CreateLiveConnection
{
get { return new SqlConnection(_liveConnectionString); }
}
public static SqlConnection CreateLiveMISConnection
{
get { return new SqlConnection(_liveMISConnectionString); }
}
public static SqlConnection CreateDevConnection
{
get { return new SqlConnection(_devConnectionString); }
}
public static SqlConnection CreateDevMISConnection
{
get { return new SqlConnection(_devMISConnectionString); }
}
}
}
Logic.cs:
using System;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;
namespace DataFramework
{
public class Logic
{
SqlConnection liveConnection = ConnectionStrings.CreateLiveMISConnection;
SqlConnection devMISConnection = ConnectionStrings.CreateDevMISConnection;
public bool IsConnecting { get; set; }
public string ConnectionMessage { get; set; }
public async Task<DataTable> ResultDataTable(bool connectToLive, string commandText, CommandType commandType)
{
DataTable dt = new DataTable();
using (SqlCommand command = new SqlCommand())
{
try
{
command.CommandType = commandType;
command.CommandTimeout = 360000000;
switch (connectToLive)
{
case true:
command.CommandText = commandText;
command.Connection = liveConnection;
if (liveConnection.State == ConnectionState.Connecting)
{
IsConnecting = true;
ConnectionMessage = "Connecting to Data Source...";
}
if (liveConnection.State != ConnectionState.Closed)
liveConnection.Close();
if (liveConnection.State != ConnectionState.Open)
{
liveConnection.Open();
IsConnecting = false;
ConnectionMessage = "";
}
break;
case false:
command.CommandType = commandType;
command.CommandText = "";
command.Connection = devMISConnection;
if (devMISConnection.State == ConnectionState.Connecting)
{
IsConnecting = true;
ConnectionMessage = commandText;
}
if (devMISConnection.State != ConnectionState.Closed)
devMISConnection.Close();
if (devMISConnection.State != ConnectionState.Open)
{
devMISConnection.Open();
IsConnecting = false;
ConnectionMessage = "";
}
break;
}
using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
dt.Load(reader);
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message, "An Error Has Occured", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
}
finally
{
if (devMISConnection.State != ConnectionState.Closed)
devMISConnection.Close();
if (liveConnection.State != ConnectionState.Closed)
liveConnection.Close();
}
}
return dt;
}
}
}
I include this dll as a reference in the app that I'm writing:
using System.Data;
using System.Threading.Tasks;
using System.Windows.Forms;
using DataFramework;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
DataTable dt = new DataTable();
DataFramework.Logic logic = new Logic();
public Form1()
{
InitializeComponent();
}
private async void Form1_Load(object sender, EventArgs e)
{
dt = await Task.Run(() => logic.ResultDataTable(true, "SELECT * FROM MIS.dbo.ETL_Table", CommandType.StoredProcedure));
}
}
}
The code throws the exception here:
SqlConnection liveConnection = ConnectionStrings.CreateLiveMISConnection;
So why, when I'm initializing the class, do I get this issue?
When you reference a DLL (or project) from another project, the app.config from the top most project is used. So, if you have your DataFramework being called from your WinformsApp, then your WinformsApp needs to have the right config settings in it. By default, it will ignore any app.config in the DataFramework. A bit frustrating at times! Copy your settings from your DataFramework app.config in to the WinformsApp app.config and it will work.
Another unrelated observation is that you have the following:
"SELECT * FROM MIS.dbo.ETL_Table", CommandType.StoredProcedure
The command type should be text and not a stored procedure.
Related
I have two classes SqlHelper and DishesTypes there are used in a DAL project
public class SqlHelper
{
public static SqlDataReader ExecuteReader(string procedure,
params SqlParameter[] commandParameters)
{
using (var connection = new SqlConnection(
ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
using (var command = new SqlCommand(procedure, _connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(commandParameters);
return command.ExecuteReader();
}
}
public class DishesTypes
{
public static SqlDataReader DishesTypesSelectAll()
{
return SqlHelper.ExecuteReader("DishesTypesSelectAllRows"); //name of procedure
}
}
And I have class DishedTypes that used in a BLL project like this
public class DishesTypes
{
public int DishTypeId { get; set; }
public string DishType { get; set; }
public static List<DishesTypes> DishesTypesSelectAll()
{
IDataReader dr = DataAccessLayer.DishesTypes.DishesTypesSelectAll();
List<DishesTypes> dishesTypesList = new List<DishesTypes>();
while (dr.Read())
{
DishesTypes myDishesTypes = new DishesTypes
{
DishTypeId = (int)dr["DishTypeId"],
DishType = (string)dr["DishType"]
};
dishesTypesList.Add(myDishesTypes);
}
return dishesTypesList;
}
}
Problems starts here while (dr.Read()),The reason, the connection to this point has already closed and it is necessary to reconnect how best to change the implementation of classes adhering layers DAL and BLL, to work?
If you want to roll your own, something like this is better:
public class DataQuery
{
private readonly string _connectionString;
public DataQuery(string connectionString)
{
_connectionString = connectionString;
}
public IEnumerable<T> GetList<T>(string procedure,
Func<IDataRecord, T> entityCreator,
params SqlParameter[] commandParameters
)
{
var result = new List<T>();
using (var connection = CreateConnection())
using (var command = CreateCommand(procedure, connection, commandParameters))
using (var reader = command.ExecuteReader())
{
result.Add(entityCreator(reader));
}
return result;
}
private SqlConnection CreateConnection()
{
var connection = new SqlConnection(_connectionString);
connection.Open();
return connection;
}
private static DbCommand CreateCommand(string procedure,
SqlConnection connection, SqlParameter[] commandParameters)
{
var command = new SqlCommand(procedure, connection)
{
CommandType = CommandType.StoredProcedure
};
command.Parameters.AddRange(commandParameters);
return command;
}
}
Which you would call like this:
var connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"]
.ConnectionString;
var query = new DataQuery(connectionString);
Func<IDataRecord, DishesTypes> creator = dr =>
new DishesTypes
{
DishTypeId = (int)dr["DishTypeId"],
DishType = (string)dr["DishType"]
};
var results = query.GetList("DishesTypesSelectAllRows", creator);
Otherwise, which I recommend, have a look at Dapper.
Dapper would allow you to simply do:
var results = connection.Query<DishesTypes>("DishesTypesSelectAllRows",
commandType: CommandType.StoredProcedure);
First of all, your using statement is closing your connection, so you cannot expect to return a useable IDataReader. Second, your connection is never opened, so you would not get a result, anyway. Having said that, if your dataset will always be small enough to fit in memory, you could use something like what I have done below. This should have minimal impact on your code.
public class SqlHelper
{
public static IDataReader ExecuteReader(string procedure, params SqlParameter[] commandParameters)
{
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
connection.Open();
using (var command = new SqlCommand(procedure, connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(commandParameters);
DataTable dt = new DataTable();
using (SqlDataAdapter da = new SqlDataAdapter(command))
da.Fill(dt);
return dt.CreateDataReader();
}
}
}
}
public class DishesTypes
{
public static IDataReader DishesTypesSelectAll()
{
return SqlHelper.ExecuteReader("DishesTypesSelectAllRows");//name of procedure
}
}
This is my current pattern
private void ReadData(string connString, string cmdString)
{
using (OracleConnection conn = new OracleConnection(connString))
{
conn.Open();
OracleCommand cmd = new OracleCommand(cmdString, conn);
OracleDataReader reader = cmd.ExecuteReader();
//some long operation using reader
}
}
In the above case, the connection remains open while the long operation is going on. Is there a way I could close the connection but still preserve the reader. Is that going to be advantageous?
If by long operations you mean to say that you need to do extra operations on the database (like update/insert/delete), then you cannot close the connection.
If you want to read the data and do some calculation based on it, then you should modify you pattern to: 1. read all the data, 2. close the connection, 3. do long operation on the data.
using System;
using System.Collections.Generic;
using Oracle.DataAccess.Client;
namespace Utils
{
class Test
{
private class Class
{
public string FirstProperty { get; set; }
public string SecondProperty { get; set; }
}
private void ReadData(string connString, string cmdString)
{
List<Class> data = new List<Class>();
using (OracleConnection conn = new OracleConnection() { ConnectionString = connString })
using (OracleCommand objCmd = new OracleCommand()
{
Connection = conn,
CommandText = cmdString
})
{
try
{
conn.Open();
}
catch (OracleException)
{
OracleConnection.ClearPool(conn);
conn.Open();
}
using (OracleDataReader dataReader = objCmd.ExecuteReader())
{
while (dataReader.Read())
data.Add(new Class()
{
FirstProperty = dataReader.GetString(0),
SecondProperty = dataReader.GetString(1)
});
}
conn.Close();
}
//some long operation using data
}
}
}
Is the following defensive programming?
What I mean is that if it loses the connection, or some problem occurs during run-time and then the user runsit again will the .NET framework have tidied up any open connections and objects that were created when it first ran?
I've heard mention of a "Singleton pattern" - is this something I should use in the static method CreateConnection?
class Program {
static void Main(string[] args) {
DataTable CasTable = fillSampleDataTable("SELECT top 100 * FROM x");
//do other stuff
}
static SqlConnection CreateConnection() {
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["XXX"].ConnectionString);
return conn;
}
static SqlDataAdapter CreateAdapter(string myCommand) {
SqlDataAdapter myAdapt = new SqlDataAdapter(myCommand, CreateConnection());
return myAdapt;
}
static DataTable fillSampleDataTable(string myCommand) {
using (var adapt = CreateAdapter(myCommand)) {
DataSet mySet = new DataSet();
adapt.Fill(mySet, "SampleData");
return mySet.Tables["SampleData"];
}
}
}
I would recommend you using the ADO.NET connection pool, a.k.a disposing the connections as soon as you have finished using them => wrap all IDisposable resources in using statements:
class Program
{
static void Main(string[] args)
{
DataTable CasTable = fillSampleDataTable("SELECT top 100 * FROM x");
//do other stuff
}
static DataTable fillSampleDataTable(string myCommand)
{
var connectionString = ConfigurationManager.ConnectionStrings["XXX"].ConnectionString;
using (var conn = new SqlConnection(connectionString))
using (var cmd = conn.CreateCommand())
using (var adapt = new SqlDataAdapter(cmd, conn))
{
conn.Open();
cmd.CommandText = myCommand;
DataSet mySet = new DataSet();
adapt.Fill(mySet, "SampleData");
return mySet.Tables["SampleData"];
}
}
}
But normally DataSets and DataTables are artifacts of the past. Today you are better off using strongly typed models.
So define a model:
public class MyModel
{
public int Id { get; set; }
public string Name { get; set; }
}
and then write a method that will return a list of those models:
class Program
{
static void Main(string[] args)
{
var models = SelectTop100Models("SELECT top 100 * FROM x");
//do other stuff
}
static IEnumerable<MyModel> SelectTop100Models()
{
var connectionString = ConfigurationManager.ConnectionStrings["XXX"].ConnectionString;
using (var conn = new SqlConnection(connectionString))
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "SELECT top 100 * FROM x";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
yield return new MyModel
{
Id = reader.GetInt32(reader.GetOrdinal("ID")),
Name = reader.GetString(reader.GetOrdinal("Name")),
};
}
}
}
}
}
Alternatively you might consider using an ORM framework such as the ADO.NET Entity Framework as it will simplify you querying the relational database and working directly with your strongly typed models using LINQ queries.
I am trying to create a generic method for calling stored procedures
I would like to pass in the Parameters in via an array
At the moment i am having trouble adding the parameters to the SqlCommand
This is what i have so far
Can anyone advise
thanks
Simon
Calling the method
string[] paramNames = new string[1];
paramNames[0] = "#date = 2012-1-1";
string err="";
WriteToDatabase("exec LoadData", CommandType.StoredProcedure, paramNames, out err);
Method
public static bool WriteToDatabase(
string sql,
CommandType commandType,
string[] paramNames,
out string errorText)
{
bool success = false;
errorText = "";
try
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
List<SqlParameter> parameters = new List<SqlParameter>();
foreach (string paramName in paramNames)
{
parameters.Add(new SqlParameter() { ParameterName = paramName });
}
using (SqlCommand command = new SqlCommand()
{
Connection = connection,
CommandText = sql,
CommandType = commandType,
Parameters = parameters
})
command.ExecuteNonQuery();
connection.Close();
}
}
catch (SqlException sex)
{
log.Error("QueryDatabase SQLexception:" + sex.Message);
}
catch (Exception ex)
{
log.Error("QueryDatabase exception:" + ex.Message);
}
return success;
}
Here's a class I whipped up a while back. It's pretty easy to use:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Web;
using System.Xml;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;
namespace NESCTC.Data
{
public class DataAccess : IDisposable
{
#region declarations
private SqlCommand _cmd;
private string _SqlConnString;
#endregion
#region constructors
public DataAccess(string ConnectionString)
{
_cmd = new SqlCommand();
_cmd.CommandTimeout = 240;
_SqlConnString = ConnectionString;
}
#endregion
#region IDisposable implementation
~DataAccess()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_cmd.Connection.Dispose();
_cmd.Dispose();
}
}
#endregion
#region data retrieval methods
public DataTable ExecReturnDataTable()
{
using (var conn = new SqlConnection(this.ConnectionString))
{
try
{
PrepareCommandForExecution(conn);
using (SqlDataAdapter adap = new SqlDataAdapter(_cmd))
{
DataTable dt = new DataTable();
adap.Fill(dt);
return dt;
}
}
finally
{
_cmd.Connection.Close();
}
}
}
public object ExecScalar()
{
using (var conn = new SqlConnection(this.ConnectionString))
{
try
{
PrepareCommandForExecution(conn);
return _cmd.ExecuteScalar();
}
finally
{
_cmd.Connection.Close();
}
}
}
#endregion
#region data insert and update methods
public void ExecNonQuery()
{
using (var conn = new SqlConnection(this.ConnectionString))
{
try
{
PrepareCommandForExecution(conn);
_cmd.ExecuteNonQuery();
}
finally
{
_cmd.Connection.Close();
}
}
}
#endregion
#region helper methods
public void AddParm(string ParameterName, SqlDbType ParameterType, object Value)
{ _cmd.Parameters.Add(ParameterName, ParameterType).Value = Value; }
private SqlCommand PrepareCommandForExecution(SqlConnection conn)
{
try
{
_cmd.Connection = conn;
_cmd.CommandType = CommandType.StoredProcedure;
_cmd.CommandTimeout = this.CommandTimeout;
_cmd.Connection.Open();
return _cmd;
}
finally
{
_cmd.Connection.Close();
}
}
#endregion
#region properties
public int CommandTimeout
{
get { return _cmd.CommandTimeout; }
set { _cmd.CommandTimeout = value; }
}
public string ProcedureName
{
get { return _cmd.CommandText; }
set { _cmd.CommandText = value; }
}
public string ConnectionString
{
get { return _SqlConnString; }
set { _SqlConnString = value; }
}
#endregion
}
}
Here is an example of how to use it:
public void UpdateWorkOrder(int workOrderID, int paymentTermTypeID, string acceptedBy, string lastIssuedBy)
{
using (var data = new DataAccess(this.ConnectionString))
{
data.ProcedureName = "UpdateWorkOrderDetails";
data.AddParm("#WorkOrderID", SqlDbType.Int, workOrderID);
data.AddParm("#PaymentTermTypeID", SqlDbType.Int, paymentTermTypeID);
data.AddParm("#AcceptedBy", SqlDbType.VarChar, acceptedBy);
data.AddParm("#LastIssuedBy", SqlDbType.VarChar, lastIssuedBy);
data.ExecNonQuery();
}
}
public DataTable GetWorkOrder(int workOrderID)
{
using (var data = new DataAccess(this.ConnectionString))
{
data.ProcedureName = "GetWorkOrder";
data.AddParm("#WorkOrderID", SqlDbType.Int, workOrderID);
return data.ExecReturnDataTable();
}
}
You must specify the names and the values of the parameters separately. You have different options to do that. The parameters collection has a method
public SqlParameter AddWithValue(string parameterName, object value)
If you want to use it, you will have to supply both of them to your method
public static bool WriteToDatabase(
string sql,
CommandType commandType,
string[] paramNames,
object[] paramValues,
out string errorText)
{
...
for (int i = 0; i < paramNames.Length; i++) {
command.Parameters.AddWithValue(paramNames[i], paramValues[i]);
}
...
}
This page is very helpful http://csharp-station.com/Tutorial/AdoDotNet/Lesson01
Check it out, lots of examples!
Not a big deal but for neatness sake is there any way to "create and open" a SqlConnection?
I naively wrote this code:
using (var strConnection = new SqlConnection(sourceConnection))
using (var strCommand = new SqlCommand(query, strConnection))
using (var reader = strCommand.ExecuteReader())
{
...
}
Which of course fails on line 3 because the connection isn't open.
Is there a neat way to avoid that nesting that opening the connection introduces?
using (var strConnection = new SqlConnection(sourceConnection))
{
strConnection.Open();
using (var strCommand = new SqlCommand(query, strConnection))
using (var reader = strCommand.ExecuteReader())
{
...
}
}
Good question, my idea is an Extension-Method for SqlConnection.
Check this:
public static class SqlExtensions {
public static SqlConnection OpenAndReturn(this SqlConnection con) {
try {
con.Open();
return con;
} catch {
if(con != null)
con.Dispose();
throw;
}
}
}
Usage:
using(var strConnection = new SqlConnection("CONNECTION").OpenAndReturn())
using(var strCommand = new SqlCommand("QUERY", strConnection))
using(var reader = strCommand.ExecuteReader()) {
//...
}
What about something like that:
class SqlHelper : IDisposable
{
public SqlHelper(string connectionString, string query) { ... }
public SqlConnection Connection { get; set; }
public SqlCommand Command { get; set; }
// SQL querying logic here
public void Execute() { ... }
/** IDisposable implementation **/
}
and in your code
using (SqlHelper sql = new SqlHelper(sourceConnection, query))
{
var reader = sql.Execute();
...
}