How to manage transaction when dealing with N-tier architecture? - c#

Business entity class:
public class TextileApplication
{
private System.Int64 _UserId;
private System.String _ApplicationNo;
private System.Int32 _SchemeId;
}
Code on .cs page:
protected void ibtnSubmitFirstPanel_Click(object sender, EventArgs e)
{
TextileApplication _TextileApplication = new TextileApplication();
_TextileApplication.UserId = 1;//static
_TextileApplication.ApplicationNo = GenerateApplicationNo();
_TextileApplication.SchemeId = Convert.ToInt16(rblScheme.SelectedValue);
string i = blTextileApplication.InsertTextileApplication(_TextileApplication);
if (Convert.ToInt16(i.Split(',').GetValue(0)) > 0)
{
// insert into another table
}
else
{
// rollback
}
}
Business Access class:
public static string InsertTextileApplication(TextileApplication _TextileApplication)
{
string i = "0";
try
{
daTextileApplication _daTextileApplication = new daTextileApplication();
object [] o = _daTextileApplication.InsertTextileApplication(_TextileApplication);
i = o[0].ToString();
}
catch (Exception ex)
{
LogErrorToLogFile logFile = new LogErrorToLogFile();
logFile.LogError(ex);
throw ex;
}
return i;
}
Data access class:
public object[] InsertTextileApplication(TextileApplication _TextileApplication)
{
try
{
pList = new List<SqlParameter>();
pList.Add(new SqlParameter("#UserId", _TextileApplication.UserId));
pList.Add(new SqlParameter("#ApplicationNo", _TextileApplication.ApplicationNo));
pList.Add(new SqlParameter("#SchemeId", _TextileApplication.SchemeId));
SqlParameter _AppNoOut = new SqlParameter("#AppNoOut", SqlDbType.VarChar,50);
_AppNoOut.Direction = ParameterDirection.Output;
pList.Add(_AppNoOut);
object[] o = sa.ExecuteQueryWithOutParameters("SPInsertTextileApplication", pList);
return o;
}
catch (Exception ex)
{
// logFile.LogError(ex);
// throw ex;
}
}
SQL access class:
public class SqlAccess
{
public object[] ExecuteQueryWithOutParameters(String procedureName, List<SqlParameter> param)
{
int count = 0;
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = procedureName;
cmd.Parameters.Clear();
if (param != null)
{
foreach (SqlParameter p in param)
{
cmd.Parameters.Add(p);
if (p.Direction == ParameterDirection.Output)
{
count++;
}
}
}
try
{
connect();
cmd.Connection = con;
cmd.ExecuteNonQuery();
//iRet = Convert.ToInt32(retValReference.Value);
object[] obj = new object[count];
count = 0;
if (param != null)
{
for (int i = 0; i < param.Count; i++)
{
if (param[i].Direction == ParameterDirection.Output)
{
obj[count] = param[i].Value.ToString();
count++;
}
}
}
return obj;
}
catch (Exception ex)
{
throw ex;
}
finally
{
closeconnect();
}
}
}
I know this is complex architecture. I need to apply transaction concept then how to do it??

Read up on the System.Transaction namespace. A transactionscipe if quite powerfully and perfectuy suitable to project transactions even through a not exactly well designed multi tiered architecture such as yours.
Alternatively a unit of work pattern would be suitable.
But man, you really try to write as much code as possible instead of using established patterns. the lower 3 classes in your list should never be written by a human.

Related

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.

Random errors with my connection in c# with sql server

I have created this class for work with my DB connection.
Applications works fine but one or two times in a day i get this errors.
Invalid operation, the connection is completed. when running query: (different queries)
or other times I received this error.
Value Timeout expired. The timeout period expired before completion of the operation or the server is not responding. This error has occurred while attempting to connect to the server Principal.
and rare errors:
Error at the transport level when receiving results from the server. (provider: Session Provider, error:? 19 - You can not use the connection nf sica) when running query
Thread aborted. when executing query:
ExecuteNonQuery requires an open and available Connection. The current state of the connection is closed. when running query
My db Class(resumed):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.Net.Mail;
using System.Web;
using System.IO;
namespace BBDD
{
public class clsBBDD
{
private string strConexion;
private int intTransaciones;
private SqlTransaction Transaccion;
private SqlConnection Conexion;
public clsBBDD(string ParamConexion)
{
strConexion = ParamConexion;
intTransaciones = 0;
}
public int Execute(string Sql)
{
return ExecutePrivado(Sql);
}
public int Execute(string Sql, bool Force)
{
return ExecutePrivado(Sql, Force);
}
private int ExecutePrivado(string Sql,bool Force = false)
{
int result = 0;
SqlCommand sentencia;
try
{
if (!Force && !Sql.ToUpper().Contains("WHERE") && !Sql.ToUpper().Contains("INSERT"))
{
throw new Exception("Sentencia Update o Delete sin WHERE");
}
if (intTransaciones > 0)
{
sentencia = new SqlCommand(Sql, Conexion, Transaccion);
}
else
{
abrirConexion();
sentencia = new SqlCommand(Sql, Conexion);
}
sentencia.CommandTimeout = 0;
result = sentencia.ExecuteNonQuery();
cerrarConexion();
}
catch (Exception e)
{
SendMailError(Sql, e);
result = 0;
}
return result;
}
public bool AbrirTrans()
{
try
{
intTransaciones += 1;
if (intTransaciones == 1)
{
abrirConexion();
Transaccion = Conexion.BeginTransaction();
}
}
catch (Exception e)
{
SendMailError("Error al abrir transacción", e);
return false;
}
return true;
}
public bool CerrarTrans(bool CommitTrans)
{
try
{
intTransaciones -= 1;
if (intTransaciones == 0)
{
if (CommitTrans)
{
Transaccion.Commit();
}
else
{
Transaccion.Rollback();
}
cerrarConexion();
}
}
catch (Exception e)
{
SendMailError("Error al cerrar transacción", e);
return false;
}
return true;
}
public object GetValue(string Sql)
{
object resultado = null;
try
{
SqlCommand sentencia = getCommand(Sql);
SqlDataReader reader = sentencia.ExecuteReader();
while (reader.Read())
{
resultado = reader[0];
}
reader.Close();
cerrarConexion();
}
catch (Exception e)
{
SendMailError(Sql, e);
resultado = null;
}
return resultado;
}
public DataRow GetValuesInRow(string sql)
{
DataRowCollection dsr;
DataRow result;
try
{
dsr = GetDSr(sql);
if (dsr.Count > 0)
{
result = dsr[0];
}
else
{
result = null;
}
}
catch (Exception e)
{
SendMailError(sql,e);
result = null;
}
return result;
}
public DataSet GetDS(string Sql)
{
DataSet result = new DataSet();
SqlDataAdapter adapter;
try
{
SqlCommand command = getCommand(Sql);
adapter = new SqlDataAdapter(command);
adapter.Fill(result);
cerrarConexion();
}
catch (Exception e)
{
SendMailError(Sql, e);
result = null;
}
return result;
}
public DataRowCollection GetDSr(string sql)
{
DataRowCollection result;
try
{
DataSet ds = GetDS(sql);
result = ds.Tables[0].Rows;
}
catch (Exception e)
{
SendMailError(sql, e);
result = null;
}
return result;
}
private void abrirConexion()
{
Conexion = new SqlConnection(strConexion);
if (Conexion.State == ConnectionState.Closed)
{
Conexion.Open();
}
}
private void cerrarConexion(bool Force = false)
{
try
{
if (intTransaciones == 0 && Conexion != null)
{
Conexion.Close();
}
}
catch
{
}
}
private SqlCommand getCommand(string Sql)
{
SqlCommand result;
if (intTransaciones == 0)
{
abrirConexion();
}
result = Conexion.CreateCommand();
if (intTransaciones > 0)
{
result.Transaction = Transaccion;
}
result.CommandText = Sql;
result.CommandTimeout = 0;
return result;
}
}
}
How I Initialize class:
public partial class frmBase : System.Web.UI.Page
{
protected clsBBDD BD;
protected void Page_Init(object sender, EventArgs e)
{
BD = new clsBBDD(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString);
clsBase.BD = BD;
}
}
All my webforms heritage from frmBase and all clases heritatge from clsBase
For use BD I call like this:
string sql;
DataRowCollection DSTrabajos;
sql = "UPDATE tabletest SET validacion = '" + ValidacionText + "', ModificadoPor = '" + Username + "' WHERE referencia = " + ReferenciaID;
BD.Execute(sql);
sql = "SELECT orderID FROM TrabajosINBOX where referencia = " + ReferenciaID;
DSTrabajos = BD.GetDSr(sql);
foreach (DataRow r in DSTrabajos)
{
//more code inside
}
My connection string params
Data Source=ServerIP;Initial Catalog=BBDDNAME;User ID=USER;Password=***********;Max Pool Size=500;MultipleActiveResultSets=true

Eliminate code duplication (Work with DB)

I've coded a dll to execute SQL commands.
And I have some code duplication in my public methods. Is there some way to avoid code duplication?
Method 1:
public Object ExecuteScalar(String command)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand commandScalar = GetSqlCommand(strCommand: command);
object result = new object();
using (commandScalar)
{
try
{
connection.StatisticsEnabled = statisticsEnabled;
connection.Open();
result = commandScalar.ExecuteScalar();
}
catch (Exception exception)
{
throw new Exception(String.Format("Не удалось выполнить команду: {0}", commandScalar.CommandText), exception);
}
finally
{
connection.Close();
}
}
return result;
if (connection.StatisticsEnabled)
AddDictionary(connection.RetrieveStatistics());
return result;
}
}
Method 2:
public void ExecuteNonQuery(String command, List<SqlParameter> lsParams = null)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand commandNonQuery = GetSqlCommand(strCommand: command, lsParams: lsParams, log: logger);
using(commandNonQuery)
{
try
{
connection.StatisticsEnabled = statisticsEnabled;
connection.Open();
commandNonQuery.ExecuteNonQuery();
}
catch (Exception exception)
{
throw new Exception(String.Format("Не удалось выполнить команду: {0}", commandNonQuery.CommandText), exception);
}
finally
{
connection.Close();
}
}
if (connection.StatisticsEnabled)
AddDictionary(connection.RetrieveStatistics());
}
}
Method 3:
public List<IDataRecord> ExecuteReader(String strCommand, List<SqlParameter> lsParams = null)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand sqlCommandRead = GetSqlCommand(strCommand: strCommand, lsParams: lsParams, log: logger);
List<IDataRecord> lsDataRecord = new List<IDataRecord>();
using (sqlCommandRead)
{
try
{
connection.StatisticsEnabled = statisticsEnabled;
connection.Open();
using (SqlDataReader sqlDataReader = sqlCommandRead.ExecuteReader())
{
while (sqlDataReader.Read())
{
lsDataRecord.Add((IDataRecord)sqlDataReader);
}
}
}
catch (Exception exception)
{
throw new Exception(String.Format("Не удалось выполнить команду: {0}", sqlCommandRead.CommandText), exception);
}
finally
{
connection.Close();
}
}
if (connection.StatisticsEnabled)
this.AddDictionary(connection.RetrieveStatistics());
return lsDataRecord;
}
}
Method that generates SQL command to execute:
private static SqlCommand GetSqlCommand(string strCommand, List<SqlParameter> lsParams = null, Logger log = null)
{
lsParams = null ?? new List<SqlParameter>();
log = null ?? LogManager.GetLogger("noname");
if (strCommand == null || strCommand == "")
{
throw new ArgumentException("Передан пустой (или null) текст команды.", "strCommand");
}
int parametersRequired = strCommand.Split('#').Length - 1;
if (parametersRequired != lsParams.Count)
{
String strParameters = null;
foreach (var item in lsParams)
{
strParameters += item.ParameterName + " : " + item.Value + "\n";
}
strParameters = null ?? "No parameters";
throw new ArgumentException(String.Format("При формировании SQL - команды выявлено, что число требуемых параметров: {0} не соответствует числу переданных: {1}\n"
+ "Команда:\n{2}\nПараметры:\n{3}", parametersRequired, lsParams.Count, strCommand, strParameters), "lsParams");
}
SqlCommand sqlCommand = new SqlCommand(strCommand);
foreach (var item in lsParams)
{
sqlCommand.Parameters.Add(item);
}
return sqlCommand;
}
public class SqlHelper
{
private readonly bool statisticsEnabled;
public SqlHelper(bool statisticsEnabled)
{
this.statisticsEnabled = statisticsEnabled;
}
public T ExecuteSqalar<T>(SqlCommand command)
{
return Execute(command, c => (T) c.ExecuteScalar());
}
public void ExecuteNonQuery(SqlCommand command)
{
Execute(command, c => c.ExecuteNonQuery());
}
public List<IDataRecord> ExecuteReader(SqlCommand command)
{
return Execute<List<IDataRecord>>(command, c =>
{
var lsDataRecord = new List<IDataRecord>();
using (SqlDataReader sqlDataReader = command.ExecuteReader())
{
while (sqlDataReader.Read())
{
lsDataRecord.Add(sqlDataReader);
}
}
});
}
public T Execute<T>(SqlCommand command, Func<SqlCommand, T> processFunction)
{
using (var connection = new SqlConnection("CONNECTION_STRING"))
{
object result = new object();
using (command)
{
try
{
connection.StatisticsEnabled = statisticsEnabled;
connection.Open();
result = processFunction(command);
}
catch (Exception exception)
{
throw new Exception(String.Format("Не удалось выполнить команду: {0}", command.CommandText), exception);
}
finally
{
connection.Close();
}
}
if (connection.StatisticsEnabled)
AddDictionary(connection.RetrieveStatistics());
return (T)result;
}
}
private void AddDictionary(IDictionary retrieveStatistics)
{
// TODO:
}
}

Call multiple SQL Server stored procedures in a transaction

For usage in my current project I've created a class that allows me to call SQL Server async.
My code looks like this:
internal class CommandAndCallback<TCallback, TError>
{
public SqlCommand Sql { get; set; }
public TCallback Callback { get; set; }
public TError Error { get; set; }
}
class MyCodes:SingletonBase<MyCodes>
{
private static string _connString = #"Data Source=MyDB;Initial Catalog=ED;Integrated Security=True;Asynchronous Processing=true;Connection Timeout=0;Application Name=TEST";
private MyCodes() { }
public void SetSystem(bool production)
{
_connString =
string.Format(#"Data Source=MyDB;Initial Catalog={0};Integrated Security=True;Asynchronous Processing=true;Connection Timeout=0;Application Name=TEST", production ? "ED" : "TEST_ED");
}
public void Add(string newCode, Action<int> callback, Action<string> error)
{
var conn = new SqlConnection(_connString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandTimeout = 0;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = #"ADD_CODE";
cmd.Parameters.Add("#NEW", SqlDbType.NVarChar).Value = newCode;
cmd.Parameters.Add("#NewId", SqlDbType.Int).Direction = ParameterDirection.Output;
try
{
cmd.Connection.Open();
}
catch (Exception ex)
{
error(ex.ToString());
return;
}
var ar = new CommandAndCallback<Action<int>, Action<string>> { Callback = callback, Error = error, Sql = cmd };
cmd.BeginExecuteReader(Add_Handler, ar, CommandBehavior.CloseConnection);
}
private static void Add_Handler(IAsyncResult result)
{
var ar = (CommandAndCallback<Action<int>, Action<string>>)result.AsyncState;
if (result.IsCompleted)
{
try
{
ar.Sql.EndExecuteReader(result);
ar.Callback(Convert.ToInt32(ar.Sql.Parameters["#NewId"].Value));
}
catch (Exception ex)
{
ar.Error(ex.Message);
}
}
else
{
ar.Error("Error executing SQL");
}
}
public void Update(int codeId, string newCode, Action callback, Action<string> error)
{
var conn = new SqlConnection(_connString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandTimeout = 0;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = #"UPDATE_CODE";
cmd.Parameters.Add("#CODE_ID", SqlDbType.Int).Value = codeId;
cmd.Parameters.Add("#NEW", SqlDbType.NVarChar).Value = newCode;
try
{
cmd.Connection.Open();
}
catch (Exception ex)
{
error(ex.ToString());
return;
}
var ar = new CommandAndCallback<Action, Action<string>> { Callback = callback, Error = error, Sql = cmd };
cmd.BeginExecuteReader(Update_Handler, ar, CommandBehavior.CloseConnection);
}
private static void Update_Handler(IAsyncResult result)
{
var ar = (CommandAndCallback<Action, Action<string>>)result.AsyncState;
if (result.IsCompleted)
{
try
{
ar.Sql.EndExecuteReader(result);
ar.Callback();
}
catch (Exception ex)
{
ar.Error(ex.Message);
}
}
else
{
ar.Error("Error executing SQL");
}
}
}
This may look like too much of code, but it lets me call it as so:
private void Add_Click(object sender, EventArgs e)
{
MyCodes.Instance.Add("Test",Success,Error)
}
private void Success(int newId)
{
MessageBox.Show(newId.ToString(), "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void Error(string error)
{
MessageBox.Show(error, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
Above code works just fine for me, I'm able to do every call async.
Problem that I have right now is to do multiple calls as transaction - I would like to update 2 codes and add one new.
Normally I would call update, then in success handler call second update, and in handler to second update I would call add that would return new id.
Something like:
-UPDATE CODE
|-UPDATE CODE
|-ADD CODE (only this one return something)
But I would like to call all of those as transaction, so if add code would break updates would rollback.
Question:
Is it possible to call multiple async queries as a transaction?
Can I call my above methods as transaction or do I must create separate method to call my procedures as one? (I would like to avoid this one because it's just copying the same code from one method to another)
I would like to add that I use .NET 3.5 so await and other nice features aren't an option.
string cnnString =WebConfigurationManager.ConnectionStrings["MyString"].ConnectionString;
SqlConnection cnn = new SqlConnection(cnnString);
SqlTransaction transaction;
cnn.Open();
transaction = cnn.BeginTransaction();
try
{
// Command Objects for the transaction
SqlCommand cmd1 = new SqlCommand("sproc1", cnn);
SqlCommand cmd2 = new SqlCommand("sproc2", cnn);
cmd1.CommandType = CommandType.StoredProcedure;
cmd2.CommandType = CommandType.StoredProcedure;
cmd1.Parameters.Add(new SqlParameter("#Param1", SqlDbType.NVarChar, 50));
cmd1.Parameters["#Param1"].Value = paramValue1;
cmd1.Parameters.Add(new SqlParameter("#Param2", SqlDbType.NVarChar, 50));
cmd1.Parameters["#Param2"].Value = paramValue2;
cmd2.Parameters.Add(new SqlParameter("#Param3", SqlDbType.NVarChar, 50));
cmd2.Parameters["#Param3"].Value = paramValue3;
cmd2.Parameters.Add(new SqlParameter("#Param4", SqlDbType.NVarChar, 50));
cmd2.Parameters["#Param4"].Value = paramValue4;
cmd1.ExecuteNonQuery();
cmd2.ExecuteNonQuery();
transaction.Commit();
}
catch (SqlException sqlEx)
{
transaction.Rollback();
}
finally
{
cnn.Close();
cnn.Dispose();
}
Yes, it is possible. Simply call SqlConnection.BeginTransaction before your first call. Make sure you assign the returned SqlTransaction object to each SqlCommand.Transaction in the chain and call SqlTransaction.Commit() at the end.
public class Command
{
public string sql { get; set; }
public CommandType cmdType { get; set; }
public Dictionary<string, object> parameter { get; set; } = null;
}
private Command insertInvoice(Invoice invoice)
{
try
{
Dictionary<string, object> parameterLocal = new Dictionary<string, object>();
parameterLocal.Add("p_customerId", invoice.customerId);
parameterLocal.Add("p_invoiceNo", invoice.invoiceNo);
parameterLocal.Add("p_invoiceDate", invoice.invoiceDate);
parameterLocal.Add("p_invoiceAmount", invoice.invoiceAmount);
parameterLocal.Add("p_withInvoice", invoice.withInvoice);
return (new Command { sql = "sp_insertInvoice", cmdType = CommandType.StoredProcedure, parameter = parameterLocal });
}
catch (Exception ex)
{
throw ex;
}
}
private Command insertInvoiceModel(InvoiceModel invoiceModel)
{
try
{
Dictionary<string, object> parameterLocal = new Dictionary<string, object>();
parameterLocal.Add("p_invoiceNo", invoiceModel.invoiceNo);
parameterLocal.Add("p_model", invoiceModel.model);
parameterLocal.Add("p_quantity", invoiceModel.quantity);
parameterLocal.Add("p_unitPrice", invoiceModel.unitPrice);
return (new Command { sql = "sp_insertInvoiceModel", cmdType = CommandType.StoredProcedure, parameter = parameterLocal });
}
catch (Exception ex)
{
throw ex;
}
}
List<Command> commandList = new List<Command>();
cmd = insertInvoice(invoicesave);
commandList.Add(cmd);
cmd = insertInvoiceModel(invoiceModelSave);
commandList.Add(cmd);
try
{
erplibmain.erpDac.runOleDbTransaction(commandList);
}
catch (Exception ex)
{
throw ex;
}
public void runOleDbTransaction(List<Command> commandList)
{
OleDbConnection erpConnection = new OleDbConnection(ErpDalMain.connectionstring);
erpConnection.Open();
OleDbCommand erpCommand = erpConnection.CreateCommand();
OleDbTransaction erpTrans;
// Start a local transaction
erpTrans = erpConnection.BeginTransaction();
// Assign transaction object for a pending local transaction
erpCommand.Connection = erpConnection;
erpCommand.Transaction = erpTrans;
try
{
foreach (Command cmd in commandList)
{
erpCommand.CommandText = cmd.sql;
erpCommand.CommandType = cmd.cmdType;
foreach (KeyValuePair<string, object> entry in cmd.parameter)
{
erpCommand.Parameters.AddWithValue(entry.Key, entry.Value);
}
erpCommand.ExecuteNonQuery();
erpCommand.Parameters.Clear();
}
erpTrans.Commit();
}
catch (Exception e)
{
try
{
erpTrans.Rollback();
}
catch (OleDbException ex)
{
if (erpTrans.Connection != null)
{
throw ex;
}
}
throw e;
}
finally
{
erpConnection.Close();
}
}

Convert delegate to Action<T> (Action<T1,T2>)

I have a DB class that makes all DB calls like below:
public delegate void Part1_Callback(string message);
public delegate void Part2_Callback(DataTable dt);
public delegate void Part3_Callback(DataTable dt, int x, int y);
public delegate void ErrorHandler(string message);
public class CommandAndCallback<TCallback>
{
public SqlCommand Sql { get; set; }
public TCallback Callback { get; set; }
public ErrorHandler Error { get; set; }
}
class DB : SingletonBase<DB>
{
public static readonly string SqlConnectionString = #"Data Source=MyDB;Initial Catalog=Stats;Integrated Security=True;Asynchronous Processing=true;";
private DB()
{
}
public void Part2(Part2_Callback callback, ErrorHandler error)
{
SqlConnection conn = new SqlConnection(SqlConnectionString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "Part2";
try
{
conn.Open();
}
catch (Exception ex)
{
error(ex.ToString());
return;
}
CommandAndCallback<Part2_Callback> ar = new CommandAndCallback<Part2_Callback>() { Callback = callback, Error = error, Sql = cmd };
IAsyncResult result = cmd.BeginExecuteReader(new AsyncCallback(Part2_Handler), ar, CommandBehavior.CloseConnection);
}
private void Part2_Handler(IAsyncResult result)
{
DataTable dt = new DataTable();
CommandAndCallback<Part2_Callback> ar = (CommandAndCallback<Part2_Callback>)result.AsyncState;
SqlDataReader dr;
if (result.IsCompleted)
{
dr = ar.Sql.EndExecuteReader(result);
}
else
dr = null;
dt.Load(dr);
dr.Close();
dt.Columns[3].ReadOnly = false;
ar.Callback(dt);
}
}
And in my main class I'm using it like so:
private void Form1_Enter(object sender, EventArgs e)
{
showStatus("Loading");
DB.Instance.Part2(Part2_OK, ErrorHandler);
}
private void ErrorHandler(string msg)
{
hideStatus();
viewStack1.InvokeIfRequired(c => { c.moveToFirst(); });
//MessageBox.Show(msg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
private void Part2_OK(DataTable dt)
{
dataGridView1.InvokeIfRequired(c =>
{
c.DataSource = dt;
});
}
Right now I have 3 methods in my DB class that return 3 different sets of data, for every type I must declare delegate.
If in future I'll add more methods then I'll have to add more delegates.
Can I remove usage of delegates? I would like to simplify build of my class so that it will be easy to add new methods.
What I need is to be able to call my DB Class like so:
DB.Instance.PartX(PartX_OK, ErrorHandler);
PartX is declared as shown
private void PartX_OK(DataTable dt, int x, int y, ...)
{
//logic here
}
Can Action<T> be used to to that, so I can call my handlers with multiple parameters? If yes then how?
Yes, I meant to get back to your earlier question, so your function would become
public void Part2(Action<DataTable> callback, ErrorHandler error)
{
SqlConnection conn = new SqlConnection(SqlConnectionString);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "Part2";
try
{
conn.Open();
}
catch (Exception ex)
{
error(ex.ToString());
return;
}
CommandAndCallback<Action<DataTable>> ar = new CommandAndCallback<Action<DataTable>>() { Callback = callback, Error = error, Sql = cmd };
IAsyncResult result = cmd.BeginExecuteReader(new AsyncCallback(Part2_Handler), ar, CommandBehavior.CloseConnection);
}
private void Part2_Handler(IAsyncResult result)
{
DataTable dt = new DataTable();
CommandAndCallback<Action<DataTable>> ar = (CommandAndCallback<Action<DataTable>>)result.AsyncState;
SqlDataReader dr;
if (result.IsCompleted)
{
dr = ar.Sql.EndExecuteReader(result);
}
else
dr = null;
dt.Load(dr);
dr.Close();
dt.Columns[3].ReadOnly = false;
ar.Callback(dt);
}
There are Action classes with upto 16 generic parameters, you will probably find the one for your needs;). See MSDN page.
And the invocation
DB.Instance.PartX((p1, p2, p3, p4) => { ... }, ErrorHandler);
Part1_Callback will be equivilant to Action<string>
Part2_Callback will be equivilant to Action<DataTable>
Part3_Callback will be equivilant to Action<DataTable, int, int>
ErrorHandler will be equivilant to Action<string>
Just use those types in place of each of your existing delegates.
There are almost no cases anymore where you need to define your own delegates. If you have >16 parameters, ref/out parameters, params parameters, or optional arguments then there may not be an Action/Func overload for you, but that's not particularly common.

Categories