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.
Related
I want to save images into my mysql database.
This is my code:
The class:
public async void SIE()
{
TrialClass trialClass = new TrialClass(ImagesPaths1);
BlogRestClient<TrialClass> restClient = new BlogRestClient<TrialClass>();
await restClient.PostAsync(trialClass);
}
public class TrialClass
{
public List<ImageFormatClass> ImagesPath2;
public TrialClass(List<ImageFormatClass> imagespath)
{
ImagesPath2 = imagespath;
}
}
public class ImageFormatClass
{
public Image SavedImage;
public int Format;
public ImageFormatClass()
{
}
}
private void Post_Clicked(object sender, EventArgs e)
{
SIE();
}
Web API controller:
public void Post([FromBody] TrialClass value)
{
foreach (ImageFormatClass s in value.ImagesPath2)
{
string sqlstring = "server=; port= ; user id =;Password=;Database=;";
MySqlConnection conn = new MySqlConnection(sqlstring);
try
{
conn.Open();
}
catch (MySqlException ex)
{
throw ex;
}
string Query = "INSERT INTO test.blogimagestable (ImagesId)values( ?str);";
MySqlCommand cmd = new MySqlCommand(Query, conn);
cmd.Parameters.Add("?str", MySqlDbType.VarString, 256).Value = s.SavedImage;
cmd.ExecuteReader();
conn.Close();
}
}
Anytime I press the post button to fire the Post_Clicked event, I get this error
Newtonsoft.Json.JsonSerializationException: 'Self referencing loop detected for property 'ManifestModule' with type 'System.Reflection.RuntimeModule'. Path 'ImagesPath2[0].SavedImage.Source.Stream.Method.Module.Assembly'.'
at `await restClient.PostAsync(trialClass);
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.
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
}
}
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();
}
}
I am trying to create a generic method for calling stored procedures
I would like to pass in the Parameters in via an array
At the moment i am having trouble adding the parameters to the SqlCommand
This is what i have so far
Can anyone advise
thanks
Simon
Calling the method
string[] paramNames = new string[1];
paramNames[0] = "#date = 2012-1-1";
string err="";
WriteToDatabase("exec LoadData", CommandType.StoredProcedure, paramNames, out err);
Method
public static bool WriteToDatabase(
string sql,
CommandType commandType,
string[] paramNames,
out string errorText)
{
bool success = false;
errorText = "";
try
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
List<SqlParameter> parameters = new List<SqlParameter>();
foreach (string paramName in paramNames)
{
parameters.Add(new SqlParameter() { ParameterName = paramName });
}
using (SqlCommand command = new SqlCommand()
{
Connection = connection,
CommandText = sql,
CommandType = commandType,
Parameters = parameters
})
command.ExecuteNonQuery();
connection.Close();
}
}
catch (SqlException sex)
{
log.Error("QueryDatabase SQLexception:" + sex.Message);
}
catch (Exception ex)
{
log.Error("QueryDatabase exception:" + ex.Message);
}
return success;
}
Here's a class I whipped up a while back. It's pretty easy to use:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Web;
using System.Xml;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;
namespace NESCTC.Data
{
public class DataAccess : IDisposable
{
#region declarations
private SqlCommand _cmd;
private string _SqlConnString;
#endregion
#region constructors
public DataAccess(string ConnectionString)
{
_cmd = new SqlCommand();
_cmd.CommandTimeout = 240;
_SqlConnString = ConnectionString;
}
#endregion
#region IDisposable implementation
~DataAccess()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_cmd.Connection.Dispose();
_cmd.Dispose();
}
}
#endregion
#region data retrieval methods
public DataTable ExecReturnDataTable()
{
using (var conn = new SqlConnection(this.ConnectionString))
{
try
{
PrepareCommandForExecution(conn);
using (SqlDataAdapter adap = new SqlDataAdapter(_cmd))
{
DataTable dt = new DataTable();
adap.Fill(dt);
return dt;
}
}
finally
{
_cmd.Connection.Close();
}
}
}
public object ExecScalar()
{
using (var conn = new SqlConnection(this.ConnectionString))
{
try
{
PrepareCommandForExecution(conn);
return _cmd.ExecuteScalar();
}
finally
{
_cmd.Connection.Close();
}
}
}
#endregion
#region data insert and update methods
public void ExecNonQuery()
{
using (var conn = new SqlConnection(this.ConnectionString))
{
try
{
PrepareCommandForExecution(conn);
_cmd.ExecuteNonQuery();
}
finally
{
_cmd.Connection.Close();
}
}
}
#endregion
#region helper methods
public void AddParm(string ParameterName, SqlDbType ParameterType, object Value)
{ _cmd.Parameters.Add(ParameterName, ParameterType).Value = Value; }
private SqlCommand PrepareCommandForExecution(SqlConnection conn)
{
try
{
_cmd.Connection = conn;
_cmd.CommandType = CommandType.StoredProcedure;
_cmd.CommandTimeout = this.CommandTimeout;
_cmd.Connection.Open();
return _cmd;
}
finally
{
_cmd.Connection.Close();
}
}
#endregion
#region properties
public int CommandTimeout
{
get { return _cmd.CommandTimeout; }
set { _cmd.CommandTimeout = value; }
}
public string ProcedureName
{
get { return _cmd.CommandText; }
set { _cmd.CommandText = value; }
}
public string ConnectionString
{
get { return _SqlConnString; }
set { _SqlConnString = value; }
}
#endregion
}
}
Here is an example of how to use it:
public void UpdateWorkOrder(int workOrderID, int paymentTermTypeID, string acceptedBy, string lastIssuedBy)
{
using (var data = new DataAccess(this.ConnectionString))
{
data.ProcedureName = "UpdateWorkOrderDetails";
data.AddParm("#WorkOrderID", SqlDbType.Int, workOrderID);
data.AddParm("#PaymentTermTypeID", SqlDbType.Int, paymentTermTypeID);
data.AddParm("#AcceptedBy", SqlDbType.VarChar, acceptedBy);
data.AddParm("#LastIssuedBy", SqlDbType.VarChar, lastIssuedBy);
data.ExecNonQuery();
}
}
public DataTable GetWorkOrder(int workOrderID)
{
using (var data = new DataAccess(this.ConnectionString))
{
data.ProcedureName = "GetWorkOrder";
data.AddParm("#WorkOrderID", SqlDbType.Int, workOrderID);
return data.ExecReturnDataTable();
}
}
You must specify the names and the values of the parameters separately. You have different options to do that. The parameters collection has a method
public SqlParameter AddWithValue(string parameterName, object value)
If you want to use it, you will have to supply both of them to your method
public static bool WriteToDatabase(
string sql,
CommandType commandType,
string[] paramNames,
object[] paramValues,
out string errorText)
{
...
for (int i = 0; i < paramNames.Length; i++) {
command.Parameters.AddWithValue(paramNames[i], paramValues[i]);
}
...
}
This page is very helpful http://csharp-station.com/Tutorial/AdoDotNet/Lesson01
Check it out, lots of examples!