I am maintaining a previous developer work.
Here is the db layer class, it has .......
public class Database
{
private string mConnString;
private SqlConnection mConn;
private SqlDataAdapter mAdapter;
private SqlCommand mCmd;
private SqlTransaction mTransaction;
private bool disposed = false;
public Database() : this(Web.GetWebConfigValue("ConnectionString"))
{
}
public Database(string connString)
{
mConnString = connString;
mConn = new SqlConnection(mConnString);
mConn.Open();
mAdapter = new SqlDataAdapter();
mCmd = new SqlCommand();
mCmd.CommandType = CommandType.StoredProcedure;
mCmd.Connection = mConn;
}
public void CloseConnection()
{
mConn.Close();
}
public void BeginTransaction()
{
mTransaction = mConn.BeginTransaction();
mCmd.Transaction = mTransaction;
}
public void CommitTransaction()
{
mTransaction.Commit();
}
public void RollbackTransaction()
{
mTransaction.Rollback();
}
public void AddParam(string name, SqlDbType type, object value)
{
SqlParameter parameter = new SqlParameter('#' + name, type);
parameter.Value = value;
mCmd.Parameters.Add(parameter);
}
public void ChangeParam(string name, object value)
{
mCmd.Parameters['#' + name].Value = value;
}
public void DeleteParam(string name)
{
mCmd.Parameters.RemoveAt('#' + name);
}
public void AddReturnParam()
{
SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "return";
parameter.Direction = ParameterDirection.ReturnValue;
mCmd.Parameters.Add(parameter);
}
public void AddOutputParam(string name, SqlDbType type, int size)
{
SqlParameter parameter = new SqlParameter('#' + name, type);
parameter.Direction = ParameterDirection.Output;
parameter.Size = size;
mCmd.Parameters.Add(parameter);
}
public int GetReturnParam()
{
return (int)mCmd.Parameters["return"].Value;
}
public object GetOutputParam(string name)
{
return mCmd.Parameters['#' + name].Value;
}
public void ClearParams()
{
mCmd.Parameters.Clear();
}
public void ExecNonQuery(string cmdText)
{
if(mConn.State==ConnectionState.Closed)
mConn.Open();
mCmd.CommandText = cmdText;
mCmd.ExecuteNonQuery();
}
public DataSet GetDataSet(string cmdText)
{
mCmd.CommandText = cmdText;
mAdapter.SelectCommand = mCmd;
DataSet ds = new DataSet();
mAdapter.Fill(ds);
return ds;
}
public IDataReader GetDataReader(string cmdText)
{
mCmd.CommandText = cmdText;
if(mConn.State==ConnectionState.Closed)
mConn.Open();
return mCmd.ExecuteReader(CommandBehavior.CloseConnection);
}
public DataTable GetDataTable(string cmdText)
{
return GetDataSet(cmdText).Tables[0];
}
public DataTable GetDataTable(string cmdText,string SQL)
{
SqlCommand cmd = new SqlCommand();
cmd.CommandText = cmdText;
mAdapter.SelectCommand = cmd;
cmd.Connection = mConn;
DataSet ds = new DataSet();
mAdapter.Fill(ds);
return ds.Tables[0];
}
public DataRow GetDataRow(string cmdText)
{
DataTable dt = GetDataTable(cmdText);
DataRow dr;
if(dt.Rows.Count > 0)
dr = dt.Rows[0];
else
dr = null;
return dr;
}
public object GetScalar(string cmdText)
{
mCmd.CommandText = cmdText;
return mCmd.ExecuteScalar();
}
public void SetCommandType(CommandType type)
{
mCmd.CommandType = type;
}
~Database()
{
this.Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if(!this.disposed)
{
if(disposing)
{
if(mConn.State == ConnectionState.Open)
mConn.Close();
this.mCmd.Dispose();
this.mAdapter.Dispose();
this.mTransaction.Dispose();
}
}
disposed = true;
}
}
Can you help me in figuring out where connection might not be closed in all the cases where this class is being used.
The connection only ever gets closed when disposing the instance of the DataBase class.
However, while this class is implementing the disposable pattern, it doesn't implement the IDisposable interface - so you can't use it in a using statement.
Moreover, you have to rely on whoever is using this class to dispose it.
If they don't, the connection will not get closed until the Finalizer gets called, and that is completely out of the developer's control. It might even not get called at all - as the garbage collector might not need to clear memory during the runtime of whatever application is using this code.
This is why the proper way of handling connections to the database is as a local variable inside a using statement.
What you want to do is create the connection and open it as late as possible, and dispose it as soon as possible.
A proper method for handling calls to the database looks like this:
int ExecuteNonQuery(string sql)
{
using(var con = new SqlConnection(connectionString))
{
using(var cmd = new SqlCommand(sql, con))
{
con.Open();
return cmd.ExecueNonQuery();
}
}
}
Of course, you would want to add arguments to hold whatever parameters you'll need to pass to the database, and an argument to hold command type, but that should be built on the basis of this structure.
I have a project on GitHub called ADONETHelper (that I've been neglecting for the past year or so due to lack of spare time) that was written in order to reduce the code repetition when using ADO.Net directly.
I've written it a few years back so of course now I have improvements in mind but as I said, I don't have the spare time to work on it - but the general idea is still valid and useful.
Basically, it has a single Execute method that looks like this:
public T Execute<T>(string sql, CommandType commandType, Func<IDbCommand, T> function, params IDbDataParameter[] parameters)
{
using (var con = new TConnection())
{
con.ConnectionString = _ConnectionString;
using (var cmd = new TCommand())
{
cmd.CommandText = sql;
cmd.Connection = con;
cmd.CommandType = commandType;
if (parameters.Length > 0)
{
cmd.Parameters.AddRange(parameters);
}
con.Open();
return function(cmd);
}
}
}
Than I've added a few methods that use this method:
public int ExecuteNonQuery(string sql, CommandType commandType, params IDbDataParameter[] parameters)
{
return Execute<int>(sql, commandType, c => c.ExecuteNonQuery(), parameters);
}
public bool ExecuteReader(string sql, CommandType commandType, Func<IDataReader, bool> populate, params IDbDataParameter[] parameters)
{
return Execute<bool>(sql, commandType, c => populate(c.ExecuteReader()), parameters);
}
and so on.
Feel free to borrow ideas from that project - or even use it as is - I have a few applications using this and they run very well for quite some time now.
You aren't implementing the disposable pattern via the IDisposable interface, you just have a Dispose method, in turn you would not be able to call this in a using statement.
public class Database : IDisposable { ... }
This is all a bit suspect: I mean, if you are already using it you aren't using this in a using statement and trying to cache the connection seemingly. I would shy away from this altogether.
Also you have a Destructor, however its usage is wrong 99% of times.
While a persistence layer makes perfectly sense, you have to design it differently. What you do is pack some complexity into methods which still do the same, such as ChangeParam() or GetDataReader().
Normally, you have repositories which have knowledge about the underlying technicalities and remove that kind of complexity, such as GetAllCustomers() (with emphasis on the domain term customer).
When you have say 4 or 5 such repositories, then you start refactoring by abstracting complexity into a parent class. By doing so, you're packing the complexity such as in GetDataReader() and promote it from the repositories into an abstract repository that sits on top of it.
By starting from where you started you'll get just another layer which does not abstract nearly as much and has too much, often unnecessary, functionality.
If it were that easy, the ADO.NET API would already have it removed in the first place.
Another thing you should do is look at this simple but ever-recurring code snippet. It distillates some core concepts about IDisposable and using(){}. You'll always encounter it in correct code:
string sql = "SELECT * FROM t";
using (SqlConnection con = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(sql, con))
{
SqlDataReader reader;
con.Open();
reader = cmd.ExecuteReader();
while (reader.Read())
{
// TODO: consume data
}
reader.Close();
}
This is the stuff I'd expect to see in a persistence layer, and the consume data part is actually the most important, because it is domain-dependent. The rest is just boilerplate code with little interest.
Related
I have the following DAL class hierarchy (partially shown), which I use for abstracting data access to the database. I would like to use it in a thread-safe manner:
public class DbAdapter : IDbAdapter, IDisposable
{
private SqlConnection _conn;
private readonly string _connString;
protected DbAdapter(string connString)
{
if (string.IsNullOrWhiteSpace(connString))
throw new ArgumentException("Value cannot be null, empty or whitespace", nameof(connString));
_connString = connString;
}
public void Dispose()
{
CloseConnection();
}
public SqlConnection GetConnection()
{
if (_conn == null || _conn.State == ConnectionState.Closed)
_conn = new SqlConnection(_connString);
else if (_conn.State == ConnectionState.Broken)
{
_conn.Close();
_conn.Open();
}
return _conn;
}
public void CloseConnection()
{
if (_conn != null && _conn.State == ConnectionState.Open)
_conn.Close();
_conn = null;
}
public SqlCommand GetCommand(string query, SqlTransaction transaction)
{
var cmd = new SqlCommand
{
Connection = transaction != null ? transaction.Connection : GetConnection()
};
if (transaction != null)
cmd.Transaction = transaction;
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
cmd.CommandTimeout = 500;
return cmd;
}
// Omitted other methods
}
public class PersonDba : DbAdapter
{
//Omitted
public IList<Person> GetPersons(string whereClause, string orderClause, SqlTransaction transaction = null)
{
var query = "SELECT Id, Name, PersonId, Birthdate, Modified FROM Persons ";
if (!string.IsNullOrWhiteSpace(whereClause))
query += whereClause;
if (!string.IsNullOrWhiteSpace(orderClause))
query += orderClause;
IList<Person> result = new List<Person>();
var sqlCmd = GetCommand(query, transaction);
using (var reader = sqlCmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while (reader.Read())
{
var person = new Person
{
Id = reader.GetInt32(reader.GetOrdinal("Id")),
PersonId = reader.GetInt32(reader.GetOrdinal("PersonId")),
Name = reader.GetString(reader.GetOrdinal("Name")),
Birthdate = reader.GetDateTime(reader.GetOrdinal("Birthdate")),
LastModified = reader.GetDateTime(reader.GetOrdinal("Modified"))
};
result.Add(person);
}
}
return result;
}
}
Typically, I inject a single instance of PersonDba into other classes, which contain multi-threaded code. In order to avoid locking around all access to that single instance within dependant code, I am thinking of making the SQLConnection DbAdapter._conn of type ThreadLocal<SqlConnection> (see ThreadLocal). Would that be enough to ensure using instances of this class is thread-safe?
Assuming that you want to keep open the possibility of making your DAL asynchronous in the future, by using the ExecuteReaderAsync instead of the ExecuteReader, then converting the field _conn of the class DbAdapter to ThreadLocal<SqlConnection> is not safe. The reason is that after awaiting the asynchronous request, the asynchronous workflow will most probably continue on another ThreadPool thread. So the wrong connection will be closed, and some other unrelated concurrent data access operation may be interrupted and aborted.
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 question already has answers here:
Data Access Layer design patterns
(7 answers)
Closed 8 years ago.
I'm writing an 'off-the shelf' desktop app in C# that needs to connect to one of three different types of database (SQL Server, MySQL or SQL Server Compact) depending on the version and customer requirements.
I have been using ADO code as follows:
using (SqlConnection conn = MSSQLHelpers.GetConnection())
{
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT * FROM settings WHERE ID=#ID";
cmd.Parameters.AddWithValue("#ID", 1);
using (SqlDataReader rdr = MSSQLHelpers.GetDataReader(cmd))
{
if (rdr.Read())
{
Config.AdvancedSecurity = rdr.GetBoolean(rdr.GetOrdinal("advancedsecurity"));
Config.BookSampleInOnCreate = rdr.GetBoolean(rdr.GetOrdinal("newsamplein"));
etc....
}
rdr.Close();
}
}
conn.Close();
}
This is obviously specific to SQL Server. My question is what would be the best way to avoid repeating all the above for each of the three database types using MySqlConnection, SqlCeConnection etc?
Many thanks in advance for any help.
Jon
I have actually done similar, and I did with a hierarchy class structure using the INTERFACES. For example, if you look at SqlConnection, you be declared as inclusive of the "IDbConnection" interface. Likewise SqlCommand would include "IDbCommand", SqlDataAdapter uses "IDbDataAdapter" and similar for parameters and the like.
So, I had a parent class that was like a template that all would have (almost an abstract class), but since I had common stuff regardless of which connection type, it was actually a function class too.
public class MyDBHandler
{
public virtual IDbConnection GetConnection()
{ throw new Exception( "Please define specific GetConnection method"}; }
public virtual IDbCommand GetCommand()
{ throw new Exception( "Please define specific GetCommand method"}; }
public virtual IDbDataAdapter GetDataAdapter()
{ throw new Exception( "Please define specific DataAdapter method"}; }
public virtual string GetConnectionString()
{ throw new Exception( "Please define specific ConnectionString method"}; }
etc...
// Then some common properties you might want for connection path, server, user, pwd
protected string whatServer;
protected string whatPath;
protected string whatUser;
protected string whatPwd;
protected connectionHandle;
public MyDBHandler()
{
// always start with HAVING a connection object, regardless of actual connection or not.
connectionHandle = GetConnection();
}
// common function to try opening corresponding connection regardless of which server type
public bool TryConnect()
{
if( connectionHandle.State != System.Data.ConnectionState.Open )
try
{
connectionHandle.ConnectionString = GetConnectionString();
connectionHandle.Open();
}
catch( Exception ex )
{
// notify user or other handling
}
if( connectionHandle.State != System.Data.ConnectionState.Open )
MessageBox.Show( "Some message to user." );
// return true only if state is open
return connectionHandle.State == System.Data.ConnectionState.Open;
}
// Now, similar to try executing a command as long as it is of IDbCommand interface to work with
public bool TryExec( IDbCommand whatCmd, DataTable putResultsHere )
{
// if can't connect, get out
if( ! TryConnect() )
return false;
bool sqlCallOk = false;
if( putResultsHere == null )
putResultsHere = new DataTable();
try
{
da.Fill(oTblResults, putResultsHere);
// we got this far without problem, it was ok, regardless of actually returning valid data
sqlCallOk = true;
}
catch( Exception ex )
{
// Notify user of error
}
return sqlCallOk;
}
}
public class SQLHandler : MyDbHandler
{
public override IDbConnection GetConnection()
{ return (IDbConnection) new SqlConnection(); }
public override IDbCommand GetCommand()
{ return (IDbCommand) new SqlCommand("", connectionHandle ); }
public override IDbDataAdapter GetDataAdapter()
{ return (IDbDataAdapter) new SqlDataAdapter(); }
public override string GetConnectionString()
{ return "Driver={SQL Server}; blah, blah of properties"; }
}
public class MySQLHandler : MyDbHandler
{
public override IDbConnection GetConnection()
{ return (IDbConnection) new MySqlConnection(); }
public override IDbCommand GetCommand()
{ return (IDbCommand) new MySqlCommand("", connectionHandle ); }
public override IDbDataAdapter GetDataAdapter()
{ return (IDbDataAdapter) new MySqlDataAdapter(); }
public override string GetConnectionString()
{ return "Driver={MySql}; blah, blah of properties"; }
}
Then, in your code, depending on how you want to handle, you could do
MyDbHandler whichDatabase = new SQLHandler();
// set any settings for such connection and such...
IDbCommand cmd = whichDatabase.GetCommand();
cmd.CommandText = "select * from whereEver";
DataTable oTmp = new DataTable()
whichDatabase.TryExec( cmd, oTmp );
Now you have a table returned, and populated with rows/columns from the query as needed, close connection in the TryExec() call, or however you find fit.
Just an stripped-down version of something I did in the past.
If you want to swap between MySql, Sql-Server, Access, Visual FoxPro, etc create corresponding handlers.
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'd like to implement N-tier architecture in my WinForms applications to separate (just logically - in one project) business logic from data access, however I have some doubts about using transacion in BLL. All tutorials I've found in the Internet either are very simple implementations of that architecture (without transactions), or are too complex for my needs. Trying to find my own way, I've come to the point, where I don't know the best way to handle transactions of in BLL layer.
I'll try to use some simple example to illustrate the problem (all classes are in separate files):
//DTO - Data Transfer Objects
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SomeOtherItem
{
public int Id { get; set; }
public string Name { get; set; }
}
//DAL - Data Access layer
public class ItemDAL
{
public ItemDAL()
{
}
public void Add(Item item)
{
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
{
conn.Open();
using (NpgsqlCommand cmd = new NpgsqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = #"INSERT INTO tbl_items (name)
VALUES (#name)";
cmd.Parameters.AddWithValue("#name", item.Name);
cmd.ExecuteNonQuery();
}
}
}
}
public class SomeOtherItemDAL
{
public SomeOtherItemDAL()
{
}
public void Add(SomeOtherItem someOtherItem)
{
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
{
conn.Open();
using (NpgsqlCommand cmd = new NpgsqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = #"INSERT INTO tbl_some_other_items (name)
VALUES (#name)";
cmd.Parameters.AddWithValue("#name", someOtherItem.Name);
cmd.ExecuteNonQuery();
}
}
}
}
//BLL - Business Logic Layer
public class SomeBLL
{
public SomeBLL()
{
}
public void Add(Item item, SomeOtherItem someOtherItem)
{
ItemDAL itemDAL = new ItemDAL();
SomeOtherItemDAL someOtherItemDAL = new SomeOtherItemDAL();
// *** this must be done in one transaction ***
itemDAL.Add(item);
someOtherItemDAL.Add(someOtherItem);
}
}
Now, the problem is that if I want to use Transacion, I cannot use:
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
in DAL. To use NpgsqlTransacion object I must somehow keep connection opened and visible in both DAL classes.
I've tried use TransacionScope object for that, but from from some reasons it's not working with PostgreSQL and the driver I'm using (INSERTS are done just after executed and there is no transaction rollback when exception within TransacionScope occures).
What I've come into is to make additional Singleton class to keep connection alive and manage transactions:
public class DB
{
private static DB instance;
private const string connString = #"Server=localhost;Port=5432;Database=db_test;User Id=usr_test;Password=pass";
private NpgsqlConnection conn;
private DB()
{
conn = new NpgsqlConnection(connString);
}
public static DB Instance
{
get
{
if (instance == null)
{
instance = new DB();
}
return instance;
}
}
#region --- connection ---
public NpgsqlConnection GetOpenConnection()
{
OpenConnection();
return conn;
}
private void OpenConnection()
{
if (conn.State == ConnectionState.Closed || conn.State == ConnectionState.Broken)
conn.Open();
}
public void CloseConnection()
{
if (conn != null && !inTransaction)
{
conn.Close();
}
}
#endregion
#region --- transaction ---
private NpgsqlTransaction trans;
private bool inTransaction;
public bool InTransaction { get { return inTransaction; } }
public void TransactionStart()
{
OpenConnection();
trans = conn.BeginTransaction();
inTransaction = true;
}
public void TransactionCommit()
{
if (inTransaction)
{
try
{
trans.Commit();
trans.Dispose();
}
finally
{
inTransaction = false;
CloseConnection();
}
}
}
public void TransactionRollback()
{
if (inTransaction)
{
try
{
trans.Rollback();
trans.Dispose();
}
finally
{
inTransaction = false;
CloseConnection();
}
}
}
#endregion
}
and rebuild both DAL Add methods to access connection like that:
//DAL - Data Access layer
public class ItemDAL
{
public ItemDAL()
{
}
public void Add(Item item)
{
using (NpgsqlCommand cmd = new NpgsqlCommand())
{
cmd.Connection = DB.Instance.GetOpenConnection();
cmd.CommandText = #"INSERT INTO tbl_items (name)
VALUES (#name)";
cmd.Parameters.AddWithValue("#name", item.Name);
cmd.ExecuteNonQuery();
}
if (!DB.Instance.InTransaction)
DB.Instance.CloseConnection();
}
}
public class SomeOtherItemDAL
{
public SomeOtherItemDAL()
{
}
public void Add(SomeOtherItem someOtherItem)
{
using (NpgsqlCommand cmd = new NpgsqlCommand())
{
cmd.Connection = DB.Instance.GetOpenConnection();
cmd.CommandText = #"INSERT INTO tbl_some_other_items (name)
VALUES (#name)";
cmd.Parameters.AddWithValue("#name", someOtherItem.Name);
cmd.ExecuteNonQuery();
}
if (!DB.Instance.InTransaction)
DB.Instance.CloseConnection();
}
}
Note that I'd like to follow the rule to "close database connection as soon as possible", so when Add method is called without transaction scope, I'd like it to close the connection.
So, the final questions are:
1. What do you think about it, is there a better way to handle that issue, any suggestions?
2. Should I dispose a connection in DB.CloseConnecion()? I surely do when using using (NpgsqlConnection conn = ...) { ... } pattern, but as Singleton is alive as long as application, does it make sense? Connection is returned to ConnectionPool after Close(), isn't it? Or maybe I should also dispose a Singleton object (together with connection), after each using?
3. It's not directly connected question, but if I use DTO objects (just properties, no methods) and have also some BusinessObjects (BO) with the same properties, but also with additional methods (validations, calculations, operations etc.), can it be inherited from DTO? Or maybe I can use full BusinessObject to transfer it between layers, and get rid off DTO?
EDIT: TransacionScope
As requested, I add some code from my tries with TransactionScope. Simply WinForm application, no Exceptions handling. As a result, there is an Exception window when I throw it, but in database I see records with values test1 ans test2. Both when debbuging in VS and executing application from .exe
using Npgsql;
using System.Transactions;
//...
private void button1_Click(object sender, EventArgs e)
{
using (System.Transactions.TransactionScope scope = new TransactionScope())
{
AddValue("test1");
AddValue("test2");
throw new Exception("bam!");
AddValue("test3");
scope.Complete();
}
}
private void AddValue(string value)
{
string connString = "Server=localhost;Port=5432;Database=db_test;User Id=usr_test;Password=pass";
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
{
conn.Open();
using (NpgsqlCommand cmd = new NpgsqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = #"INSERT INTO tbl_test (name)
VALUES (#name)";
cmd.Parameters.AddWithValue("#name", value);
cmd.ExecuteNonQuery();
}
}
}
I've never used NpgSql, but reading the documentation of NpgSql it appears they have some support of TransactionScope() if you add "enlist=true" in your connection string.
I'm looking at the "System.Transactions Support" section of the below NpgSql documentation:
http://npgsql.projects.postgresql.org/docs/manual/UserManual.html
Assuming TransactionScope() did work, then you can do simething like this...
using (var scope = new System.Transactions.TransactionScope())
{
ItemDAL itemDAL = new ItemDAL();
SomeOtherItemDAL someOtherItemDAL = new SomeOtherItemDAL();
// *** this must be done in one transaction ***
itemDAL.Add(item);
someOtherItemDAL.Add(someOtherItem);
scope.Complete()
}
What you did is brave, but not scalable. I'm not familiar with PGSQL, but this problem is the exact reason why the TransactionScope API was designed.
Can you show your code using the TransactionScope api? Make sure that you are not calling scope.Complete(); if an error occurs in one of the methods. Be careful to not "eat" the exceptions inside the methods because in that case the flow will continue just like if nothing happened.
More reading about TransactionScope here:
http://msdn.microsoft.com/en-us/library/ms172152.aspx
Update 1
Thanks for sharing your code that uses the TransactionScope class. The code looks perfectly correct to me. According to this (http://npgsql.projects.postgresql.org/docs/manual/UserManual.html) document (the same quoted by ChrisNeil52), Enlist=true should be included in the connection string for transactions to work.
You might be dealing with a buggy API. Good luck with this.
I know this sounds peculiar, but something I would try would be to use a different NpgsqlCommand constructor. new NpgsqlCommand("sql query", connection), instead of creating the command and assigning it the connection. They should be equivalent. but who know...