Connection control in n-tier application - c#

I’m new in N-tier application and I’m trying to understand the way N-tier app works by making an own application.
I read serveral articles about it, but there’s nothing much more than a wall of text (Might I haven’t found the right place).
Although I got a tool that helps generating DAL, BLL classes but it’s just like an ‘outline’. I have no experience of working with DAL, BLL… so I could not do much.
The following is the code that made from generation tool (as I said) and slightly modified by myself.
DAL
Namespace FIRSTAPP.DAL
{
class Teacher
{
clsOLEDB db = new clsOLEDB();
public OleDbDataReader SelectAll()
{
try
{
db.OpenConnection();
return db.Excute("SELECT * FROM TEACHER");
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
public OleDbDataReader SelectRow()
{}
}
}
BLL
namespace FIRSTAPP.BLL
{
class Teacher
{
public string Id { get; set; }
public string FullName { get; set; }
public DateTime DateOfBirth { get; set; }
public List< Teacher > SelectAll()
{
DAL.Teacher dalTeacher = new DAL.Teacher();
IDataReader dr = dalTeacher.SelectAll();
List<Teacher> TeacherList = new List<Teacher>();
while (dr.Read())
{
Teacher myTeacher = new Teacher
{
Id = (string)dr["Id"],
FullName = (string)dr["FullName"],
DateOfBirth = (DateTime)dr["DateOfBirth "],
};
TeacherList.Add(myTeacher);
}
dr.Close();
return TeacherList;
}
Public Teacher SelectRow(string TeacherId)
{}
}
}
DBConnector
namespace FIRSTAPP.DB
{
public class clsOLEDB
{
string mConnectionString = #"Provider = Microsoft.JET.OLEDB.4.0; Data Source = .\FirstAppDB.mdb";
OleDbConnection mConnection;
OleDbDataReader dataReader;
OleDbCommand cmdCommand;
public void OpenConnection()
{
mConnection = new OleDbConnection(mConnectionString);
try
{
mConnection.Open();
}
catch
{
throw new Exception("Could not connect to DB");
}
finally
{
if ( mConnection != null )
mConnection.Close();
}
}
public OleDbDataReader Excute(string SQLText)
{
cmdCommand = mConnection.CreateCommand();
cmdCommand.CommandText = SQLText;
try
{
return cmdCommand.ExecuteReader();
}
catch
{
throw new Exception("Has an error when executing");
}
}
public void CloseConnection()
{
mConnection.Close();
}
}
}
My question is:
If we need to fetch data from data source, we have to follow these steps: Open connection -> Reading data -> Close reader -> Close connection.
My problem is I don’t know where do I should put Close connection statement, in BLL or DAL? How to do that? The more detail, it gets better.
Can you spend 1 minutes to take a look at my code. Is it clean enough? Or Does it has any another problem?
Sorry if there’re any mistakes in my English. Thank you very much.

Related

Web API and Database link C#

I'm currently new to Web APIs and how to work with them. My project consists of a web api that communicates with a DB through Business and Data layers.
First, i have managed to create a controller as show below:
using BusinessLayer;
using BusinessLayer.Models;
namespace ContactService.Controllers
{
public class ContactController : ApiController
{
public void Post([FromBody] Contact contact)
{
Business bu = new Business();
bu.getcontact(contact);
}
}
}
The getcontact function which has Contact as an object:
public void getcontact(Contact c)
{
Data dal = new Data();
dal.ExecuteView(c.Fname,ref c.Lname,ref c.Age,ref c.exist);
}
which gives an error like:
A property, indexer or dynamic member access may not be passed as an out or ref parameter?
And the contact class is:
public class Contact
{
public int ID { get; set; }
public string Fname { get; set; }
public string Lname { get; set; }
public int Age { get; set; }
}
The business layer is connected to the datalayer that manages all sql queries through many functions like:
public void Save(String fname,String lname,String age)
{
if (fname != "" && lname != "" && age != "")
{
Data connect = new Data();
connect.OpenConnection();
connect.ExecuteInsert(fname, lname, age, ref exist);
if (exist)
MessageBox.Show("Name exists");
else
MessageBox.Show("User entered successfully");
connect.CloseConnection();
//connect.isTimer();
}
else
{
MessageBox.Show("Enter all fields");
}
}
public void Del(String fname)
{
if (fname != "")
{
Data connect = new Data();
connect.OpenConnection();
connect.ExecuteDelete(fname, ref exist);
if (exist)
{
MessageBox.Show("Record exists and Deleted");
}
else
MessageBox.Show("Record doesnt exist!");
connect.CloseConnection();
}
else
{
MessageBox.Show("Enter first name!");
}
}
public void View(String fname,ref string lname,ref string age)
{
if (fname != "")
{
Data connect = new Data();
connect.OpenConnection();
connect.ExecuteView(fname, ref lname, ref age, ref exist);
tb1.Text = fname;
tb2.Text = lname;
tb3.Text = age;
if (exist)
{
MessageBox.Show("Infos displayed");
}
else
MessageBox.Show("Name not found");
connect.CloseConnection();
}
else
{
MessageBox.Show("Enter first name!");
}
}
For example, ExecuteView looks like this:
public void ExecuteView(String Fname, ref String lname,ref String age,ref
bool exist)
{
try
{
//using (var command = new SqlCommand("DisplayInfo", con))
using (IDbConnection db = new SqlConnection(c.connstr))
{
if (db.State != ConnectionState.Open)
db.Open();
DynamicParameters param = new DynamicParameters();
param.Add("#Fname", Fname);
param.Add("#exist", dbType: DbType.Boolean, direction:
ParameterDirection.Output);
SqlDbType.int,ParameterDirection.Output);
Client cl = new Client();
cl = con.Query<Client>("DisplayInfo", param, commandType:
CommandType.StoredProcedure).SingleOrDefault();
Fname = cl.Fname;
lname = cl.Lname;
age = cl.Age.ToString();
exist = param.Get<bool>("#exist");
if (db.State != ConnectionState.Closed)
db.Close();
}
}
My question is: After getting to the Business layer, what should i do to execute the datalayer functions through getcontact() function in the businesslayer? And how to execute GET/POST/DELETE?
All help is appreciated :)
I've taken your code and changed it a bit. There is a lot which can be improved, but this will get you started I think.
First your data access class, it should only do data access and if you want to do something with the results, return it back to the business layer. So no void method.
//DAL
public Client ExecuteView(String Fname, ref String lname, ref String age, ref bool exist)
{
try
{
//using (var command = new SqlCommand("DisplayInfo", con))
using (IDbConnection db = new SqlConnection(c.connstr))
{
if (db.State != ConnectionState.Open)
db.Open();
DynamicParameters param = new DynamicParameters();
param.Add("#Fname", Fname);
param.Add("#exist", dbType: DbType.Boolean, direction:
ParameterDirection.Output);
SqlDbType.int, ParameterDirection.Output);
var cl = new Client();
cl = con.Query<Client>("DisplayInfo", param, commandType: CommandType.StoredProcedure).SingleOrDefault();
exist = param.Get<bool>("#exist");
if (db.State != ConnectionState.Closed)
{
db.Close();
}
return client;
}
}
Your business class just calls the data access class and prepares the parameters, if necessary.
//Business
public Client GetContact(Contact contact)
{
var dataAccess = new DataAccess();
var client = dataAccess.ExecuteView(contact.FirstName, ...)
//Do some business logic, if you want.
return client;
}
Only the presentation layer is left (your API) and can look like this.
//Presentation
using BusinessLayer;
using BusinessLayer.Models;
namespace ContactService.Controllers
{
public class ContactController : ApiController
{
public void Post([FromBody] Contact contact)
{
Business bu = new Business();
var client = bu.getcontact(contact);
//Do something with the client, if you want.
Fname = client.Fname;
lname = client.Lname;
age = client.Age.ToString();
}
}
}
Over here you can change the Client object to some kind of viewmodel which you want to return or change the values to a better representation for your client/api.
Just keep in mind every layer has its own responsibility and shouldn't do anything which doesn't fit this responsibility.
Note: In your code you are doing an INSERT and SELECT in the above method. To keep the code clean, this should be handled in two seperate calls. You should split the functionality in the data access class and combine the calls in the business class, something like this.
public Client GetContact(Contact contact)
{
var dataAccess = new DataAccess();
var contactIdentifier = dataAccess.CreateContact(contact.FirstName, ...);
var client = dataAccess.GetContact(contactIdentifier);
//Do some business logic, if you want.
return client;
}
This is poorly structured anyways.you should create a manager class for each object you have in the datalayer. for ex you have contacts object create a class called ContactsManager and it should get all the functions to implement from a reusable Interface.
Fell free to read more about it:
A Complete Introduction to the Data Layer

Best way to handle exception in ADO.Net repository pattern

i have design small repository pattern for ado.net. now i could not manage to handle exception proper way. i want to push error to calling environment if any occur. if no error occur then result set will be push to calling environment.
i have repository called AdoRepository which extend other repository classes like employee etc. we are calling employee repository function from mvc controller. so i want to push error to mvc controller from employee repository if any occur during data fetch, if no error occur then data will be sent to mvc controller. here is my full code. please have look and share the idea for best design. if possible paste rectified code here.
Base repository
public abstract class AdoRepository<T> where T : class
{
private SqlConnection _connection;
public virtual void Status(bool IsError, string strErrMsg)
{
}
public AdoRepository(string connectionString)
{
_connection = new SqlConnection(connectionString);
}
public virtual T PopulateRecord(SqlDataReader reader)
{
return null;
}
public virtual void GetDataCount(int count)
{
}
protected IEnumerable<T> GetRecords(SqlCommand command)
{
var reader = (SqlDataReader) null;
var list = new List<T>();
try
{
command.Connection = _connection;
_connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
list.Add(PopulateRecord(reader));
}
reader.NextResult();
if (reader.HasRows)
{
while (reader.Read())
{
GetDataCount(Convert.ToInt32(reader["Count"].ToString()));
}
}
Status(false, "");
}
catch (Exception ex)
{
Status(true, ex.Message);
}
finally
{
// Always call Close when done reading.
reader.Close();
_connection.Close();
_connection.Dispose();
}
return list;
}
protected T GetRecord(SqlCommand command)
{
var reader = (SqlDataReader)null;
T record = null;
try
{
command.Connection = _connection;
_connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
record = PopulateRecord(reader);
Status(false, "");
break;
}
}
catch (Exception ex)
{
Status(true, ex.Message);
}
finally
{
reader.Close();
_connection.Close();
_connection.Dispose();
}
return record;
}
protected IEnumerable<T> ExecuteStoredProc(SqlCommand command)
{
var reader = (SqlDataReader)null;
var list = new List<T>();
try
{
command.Connection = _connection;
command.CommandType = CommandType.StoredProcedure;
_connection.Open();
reader = command.ExecuteReader();
while (reader.Read())
{
var record = PopulateRecord(reader);
if (record != null) list.Add(record);
}
}
finally
{
// Always call Close when done reading.
reader.Close();
_connection.Close();
_connection.Dispose();
}
return list;
}
}
StudentRepository which extend base AdoRepository
-----------------------------------------------
public class StudentRepository : AdoRepository<Student>
{
public int DataCounter { get; set; }
public bool hasError { get; set; }
public string ErrorMessage { get; set; }
public StudentRepository(string connectionString)
: base(connectionString)
{
}
public IEnumerable<Student> GetAll()
{
// DBAs across the country are having strokes
// over this next command!
using (var command = new SqlCommand("SELECT ID, FirstName,LastName,IsActive,StateName,CityName FROM vwListStudents"))
{
return GetRecords(command);
}
}
public Student GetById(string id)
{
// PARAMETERIZED QUERIES!
using (var command = new SqlCommand("SELECT ID, FirstName,LastName,IsActive,StateName,CityName FROM vwListStudents WHERE Id = #id"))
{
command.Parameters.Add(new ObjectParameter("id", id));
return GetRecord(command);
}
}
public IEnumerable<Student> GetStudents(int StartIndex, int EndIndex, string sortCol, string sortOrder)
{
string strSQL = "SELECT * FROM vwListStudents WHERE ID >=" + StartIndex + " AND ID <=" + EndIndex;
strSQL += " ORDER BY " + sortCol + " " + sortOrder;
strSQL += ";SELECT COUNT(*) AS Count FROM vwListStudents";
var command = new SqlCommand(strSQL);
return GetRecords(command);
}
public override Student PopulateRecord(SqlDataReader reader)
{
return new Student
{
ID = Convert.ToInt32(reader["ID"].ToString()),
FirstName = reader["FirstName"].ToString(),
LastName = reader["LastName"].ToString(),
IsActive = Convert.ToBoolean(reader["IsActive"]),
StateID = Convert.ToInt32(reader["StateID"].ToString()),
StateName = reader["StateName"].ToString(),
CityID = Convert.ToInt32(reader["CityID"].ToString()),
CityName = reader["CityName"].ToString()
};
}
public override void GetDataCount(int count)
{
DataCounter = count;
}
public override void Status(bool IsError, string strErrMsg)
{
hasError = IsError;
ErrorMessage = strErrMsg;
}
}
calling StudentRepository from mvc controller like below way
public class StudentController : Controller
{
private StudentRepository _data;
public StudentController()
{
_data = new StudentRepository(System.Configuration.ConfigurationManager.ConnectionStrings["StudentDBContext"].ConnectionString);
}
// GET: Stuent
public ActionResult List(StudentListViewModel oSVm)
{
StudentListViewModel SVm = new StudentListViewModel();
SVm.SetUpParams(oSVm);
SVm.Students = _data.GetStudents(SVm.StartIndex, SVm.EndIndex, SVm.sort, oSVm.sortdir).ToList();
SVm.RowCount = _data.DataCounter;
return View("ListStudents",SVm);
}
}
I don't get the point of this:
catch (Exception ex)
{
Status(true, ex.Message);
}
Simply not catch the exception and let it bubble up to the caller who, according to you, will know to handle it. No callbacks necessary.
Storing retrieved data in instance state seems like a bad way to go. Rather, return an object with that data. That results in a more straight forward API and has less mutable state.
finally
{
reader.Close();
_connection.Close();
_connection.Dispose();
}
There is a better way to go about this: Wrap resources in a using statement. In particular, part ways with the superstitious double dispose pattern.
Let the caller deal with the exception making sure that you log a decent error message (showing all relevant fields). The Status class will annoy the hell out of support people as it swallows the stack trace and says nothing about the data which has caused the error. DB exceptions are often caused by malformed data so its important to have this data logged when things go wrong.
As an aside, your PopulateRecord and GetDataCount methods should be abstract as the base versions do nothing. Another dev could easily think they don't need to implement these methods and would be left with a class with useless PopulateRecord and GetDataCount methods.

Different database types in .net app [duplicate]

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.

saving object to database c#

I'm having class Report and class Program, what I want to accomplish (no luck so far) is to send data from class Program and method SaveRep() to class Report method Save() and save it in this method.
I apologize if the question is badly formulated, I am really stuck at this, please help. Thanks
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Configuration;
using System.Data.SqlClient;
namespace Application
{
class Program
{
static void Main(string[] args)
{
//call method SaveRep
}
public void SaveRep(...)
{
int RepID = 1;
string Data1 = "SomeData1"
string Data2 = "SomeData2"
//This method should send the data above to method Save() in Report class
//data to this method will be provided from another method.
}
}
public class Report
{
private static int _repID;
public int RepID
{
get { return _repID; }
set { _repID = value; }
}
private static string _data1;
public string Data1
{
get { return _data1; }
set { _data1 = value; }
}
private static string __data2;
public string Data1
{
get { return _data2; }
set { _data2 = value; }
}
public void Save()
{
string strConnectionString = (#"server=(local)\sqlexpress;Integrated Security=True;database=DataBase");
SqlConnection connection = new SqlConnection(strConnectionString);
connection.Open();
// This method should save all data (RepID,Data1,Data2)
// to the DB, provided to her by SaveRep method from Program class.
// Properties are columns from a database
}
}
}
public void Save()
{
string yourConnString="Replace with Your Database ConnectionString";
using(SqlConnection connection = new SqlConnection(yourConnString))
{
string sqlStatement = "INSERT Table1(Data1) VALUES(#Data1)";
using(SqlCommand cmd = new SqlCommand(sqlStatement, connection))
{
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("#Data1",Data1);
//add more parameters as needed and update insert query
try
{
connection.Open();
cmd.ExecuteNonQuery();
}
catch(Exception ex)
{
//log error
}
}
}
}
Assuming your Data1 is the name of your Column name. Update the sqlStatement to have your relevant columns.
Now in SaveRep, you can call it like
public void SaveRep()
{
Report objReport=new Report();
objReport.Data1="Something somethinng";
objReport.Save();
}
string connectionString = ....; //adjust your connection string
string queryString = "INSERT INTO ....;"; //Adjust your query
using (SqlConnection connection = new SqlConnection(
connectionString))
{
using( SqlCommand command = new SqlCommand(
queryString, connection))
{
connection.Open();
command .ExecuteNonQuery();
}
}
One tiny thing that the previous speakers have not noticed, is that your Report class is severely broken. Why did you use static keywords on the variables? Do not do that! Well, unless you know what it does, and here I have the feeling you don't. Update your Report class to look like this:
public class Report
{
public int RepID {get;set;}
public string Data1 {get;set;}
public string Data2 {get;set;}
public void Save()
{
// what others said
}
}
'static' in your original code makes all te variables global/shared. That means that if you'd create 500 Report objects with new, they all would have the same one ID, Data1 and Data2. Any change to any single of those 500 objects would cause all the objects to change. But it's be misleading, the objects would not change. They would simply have the same data. Looking at the "ID" field, I think you'd rather want a separate objects with separate data for separate records..

N-tier architecture transacions handling

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...

Categories