public int ExecuteNonQuery(IDbTransaction dbTransaction, string commandText, CommandType commandType, IDbDataParameter[] parameters)
{
int returnValue = 0;
try
{
var command = database.CreateCommand(commandText, commandType, dbTransaction.Connection);
if (parameters != null)
{
foreach (var parameter in parameters)
{
//check for derived output value with no value assigned
if ((parameter.Direction == ParameterDirection.InputOutput) && (parameter.Value == null))
{
parameter.Value = DBNull.Value;
}
command.Parameters.Add(parameter);
}
}
command.CommandTimeout = 600;
returnValue = command.ExecuteNonQuery();
}
catch (Exception ex)
{
throw ex;
}
return returnValue;
}
I am trying to execute db executing using above code through c# window service through Oracle.ManagedDataAccess library. Database is Oracle 11g. Problem here is that Command Timeout is not working even when query is taking more than 15 mins to execute.
Please guide how to make it work?
Related
Let me start by posting my code first:
ExecuteScalar method:
public T ExecuteScalar<T>(string sql, CommandType commandType, List<NpgsqlParameter> parameters)
{
using (NpgsqlConnection conn = Konekcija_na_server.Spajanje("spoji"))
{
return Execute<T>(sql, commandType, c =>
{
var returnValue = c.ExecuteScalar();
return (returnValue != null && returnValue != DBNull.Value && returnValue is T)
? (T)returnValue
: default(T);
}, parameters);
}
}
Execute method:
T Execute<T>(string sql, CommandType commandType, Func<NpgsqlCommand, T> function, List<NpgsqlParameter> parameters)
{
using (NpgsqlConnection conn = Konekcija_na_server.Spajanje("spoji"))
{
using (var cmd = new NpgsqlCommand(sql, conn))
{
cmd.CommandType = commandType;
if (parameters.Count > 0 )
{
foreach (var parameter in parameters)
{
cmd.Parameters.AddWithValue(parameter.ParameterName,parameter.Value);
}
}
return function(cmd);
}
}
}
Call of ExecuteScalar method:
komanda = "begin;select count(*) from radni_sati where ime=#ime and prezime=#prezime" +
" and (dolazak is not null and odlazak is not null and sati_rada is not null) and napomena='' ;commit;";
listaParametara.Add(new NpgsqlParameter { ParameterName = "#ime", Value = ime });
listaParametara.Add(new NpgsqlParameter { ParameterName = "#prezime", Value = prezime });
var nePrazni_redovi=instanca.ExecuteScalar<int>(komanda, CommandType.Text, listaParametara);
listaParametara.Clear();
Now my problem is when I call ExecuteScalar().
For some reason my ExecuteScalar always returns 0 as result and that can't be coz I tested it as a normal query in PSQL Shell and it always returns values>0 when I call legit query that has to return normal value.
First time It enters ExecuteScalar after a call, returns a returnValue from lamba operator and for example its 16, then when it goes to Execute function, somehow it returns 0 and I dont understand why, coz main thing I need ExecuteScalar for is to return count(*) value out as an int.
can you tell us how are you calling ExecuteScalar? What type is T? Also, set breakpoint to: var returnValue = c.ExecuteScalar(); and check what type is returned after you step over that line (F10). In watch window of Visual Studio you should check Type column.
I'm adding async implementations to all my SQL base classes used in my WebAPI projects. I'm fairly new to the TAP paradigm so I'm still learning.
I know, thanks to other posts, that spawning a thread via Task.Run() does not have any performance benefits in an ASP.NET context. So I'm being extra careful with my implementations.
I've changed my QueryExecutor method to the async implementation below. But cannot figure out what the best way to load the DataTable is. I'm guessing I would ideally want to use reader.ReadAsync() to populate the DataTable but it seems there isn't anything available in the .NET 4.5 framework for that.
So I wanted to ask would it be worth writing my own extension method such as DataTable.LoadAsync(DbDataReader reader)? I kind of don't want to if it can be helped since it won't be nearly as fool-proof as managed .Net code.
Let me know what you guys think.
private async Task<DataTable> ExecuteQueryInternalAsync(string commandText, CommandType commandType, SqlConnection sqlConnection, SqlTransaction transaction, params SqlParameter[] parameters)
{
using (SqlCommand cmd = new SqlCommand(commandText, sqlConnection) { CommandType = commandType, CommandTimeout = this.config.MainConnectionTimeoutInSeconds })
{
if (transaction != null)
cmd.Transaction = transaction;
if (parameters != null)
{
foreach (var parameter in parameters)
{
if (parameter != null)
{
if (parameter.Value == null)
parameter.Value = DBNull.Value;
cmd.Parameters.Add(parameter);
}
}
}
if (sqlConnection.State == ConnectionState.Closed)
await sqlConnection.OpenAsync();
using (var reader = await cmd.ExecuteReaderAsync())
{
//Want to use: reader.ReadAsync()
var tb = new DataTable();
tb.Load(reader);
return tb;
}
}
}
Thanks
If you want an extension method, you can write directly on the command
public static class extensions
{
public async static Task<DataTable> ExecuteAndCreateDataTableAsync(this SqlCommand cmd)
{
using (var reader = await cmd.ExecuteReaderAsync().ConfigureAwait(false))
{
var dataTable = reader.CreateTableSchema();
while (await reader.ReadAsync().ConfigureAwait(false))
{
var dataRow = dataTable.NewRow();
for (int i = 0; i < dataTable.Columns.Count; i++)
{
dataRow[i] = reader[i];
}
dataTable.Rows.Add(dataRow);
}
return dataTable;
}
}
public static void LoadParams(this SqlCommand cmd, params SqlParameter[] parameters)
{
if (parameters != null)
{
foreach (var parameter in parameters)
{
if (parameter != null)
{
if (parameter.Value == null)
parameter.Value = DBNull.Value;
cmd.Parameters.Add(parameter);
}
}
}
}
private static DataTable CreateTableSchema(this SqlDataReader reader)
{
DataTable schema = reader.GetSchemaTable();
DataTable dataTable = new DataTable();
if (schema != null)
{
foreach (DataRow drow in schema.Rows)
{
string columnName = System.Convert.ToString(drow["ColumnName"]);
DataColumn column = new DataColumn(columnName, (Type)(drow["DataType"]));
dataTable.Columns.Add(column);
}
}
return dataTable;
}
}
and your medhod:
private async Task<DataTable> ExecuteQueryInternalAsync(string commandText, CommandType commandType, SqlConnection sqlConnection, SqlTransaction transaction, params SqlParameter[] parameters)
{
using (SqlCommand cmd = new SqlCommand(commandText, sqlConnection) { CommandType = commandType, CommandTimeout = this.config.MainConnectionTimeoutInSeconds })
{
if (transaction != null)
cmd.Transaction = transaction;
cmd.LoadParams(parameters);
if (sqlConnection.State == ConnectionState.Closed)
await sqlConnection.OpenAsync();
var datatable = await cmd.ExecuteAndCreateDataTableAsync();
return datatable;
}
}
I'm using .Net Compact 3.5 Windows 7 CE.
I have an application with about 50 users, I have it setup so that I would get an email every time a database transaction failed, with the query.
Every so often I would get an email with a stack trace that starts like this:
System.ArgumentException: Value does not fall within the expected range.
at System.Data.SqlClient.SqlParameterCollection.Validate(Int32 index, SqlParameter value)
at System.Data.SqlClient.SqlParameterCollection.AddWithoutEvents(SqlParameter value)
at System.Data.SqlClient.SqlParameterCollection.Add(SqlParameter value)
at MedWMS.Database.startSqlConnection(String query, SqlParameter[] parameters, SqlConnection connection, SqlCommand cmd)
at MedWMS.Database.<>c__DisplayClasse.b__8()
at MedWMS.Database.retry(Action action)
at MedWMS.Database.executeNonQuery(String query, SqlParameter[] parameters, String connectionString)...
The SQL query which causes this issue is not always the same. I run the same query seconds after I get the email in SQL Server Management Studio with no issues.
I would like to know why this could be happening. This is my first question on SO so please let me know if I'm doing something wrong. I would be happy to answer any questions to provide more detail.
This is a sample of the code that would cause this error:
SqlParameter[] parameters = new SqlParameter[1];
parameters[0] = new SqlParameter("#salesOrder", this.salesOrderNumber);
string query = #"
Select InvTermsOverride from SorMaster where SalesOrder = Convert(int, #salesOrder) and InvTermsOverride = '07' --07 is for COD";
DataTable dt = Database.executeSelectQuery(query, parameters, Country.getCurrent().getSysproConnectionStrReportServer());
This is the query that actually gets passed:
Select InvTermsOverride from SorMaster where SalesOrder = Convert(int, '000000001138325') and InvTermsOverride = '07' --07 is for COD
Here is the relevant methods from the Database class:
public static DataTable executeSelectQuery(String query, SqlParameter[] parameters, string connectionString)
{
DataTable dt = new DataTable();
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand cmd = null;
try
{
retry(() =>
{
cmd = startSqlConnection(query, parameters, connection, cmd);
using (SqlDataReader reader = cmd.ExecuteReader())
{
dt.Load(reader);
}
});
}
catch (Exception ex)
{
onDbConnectionCatch(cmd, ex);
}
finally
{
cmd.Dispose();
connection.Close();
}
}
return dt;
}
public static void executeNonQuery(String query, SqlParameter[] parameters, string connectionString)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand cmd = null;
try
{
retry(() =>
{
cmd = startSqlConnection(query, parameters, connection, cmd);
cmd.ExecuteNonQuery();
});
}
catch (Exception ex)
{
onDbConnectionCatch(cmd, ex);
}
finally
{
cmd.Dispose();
connection.Close();
}
}
}
private static void retry(Action action)
{
int retryCount = 3;
int retryInterval = 1000;
Exception lastException = null;
for (int retry = 0; retry < retryCount; retry++)
{
try
{
if (retry > 0)
System.Threading.Thread.Sleep(retryInterval);
action();
lastException = null;
return;
}
catch (Exception ex)
{
lastException = ex;
}
}
if (lastException != null)
{
throw lastException;
}
}
private static SqlCommand startSqlConnection(String query, SqlParameter[] parameters, SqlConnection connection, SqlCommand cmd)
{
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
cmd = new SqlCommand(query, connection);
if (parameters != null)
{
foreach (SqlParameter sp in parameters)
{
if (sp != null)
{
cmd.Parameters.Add(sp);
}
}
}
return cmd;
}
private static void onDbConnectionCatch(SqlCommand cmd, Exception ex)
{
try
{
new BigButtonMessageBox("", "Unable connect to database").ShowDialog();
sendEmailWithSqlQuery(cmd, ex);
}
catch
{
}
}
private static void sendEmailWithSqlQuery(SqlCommand cmd, Exception ex)
{
string query2 = "cmd was null";
if (cmd != null)
{
query2 = cmd.CommandText;
foreach (SqlParameter p in cmd.Parameters)
{
query2 = query2.Replace(p.ParameterName, "'" + p.Value.ToString() + "'");
}
}
InternetTools.sendEmail("DB ERROR", ex.ToString() + "\r\n" + query2);
}
I had the same issue as Can't solve "Sqlparameter is already contained by another SqlparameterCollection"
For some reason SQL CE has a different error.
Because of my retry method, I couldn't reuse the SqlParameter object, still not sure why it's not allowed
Anyways I changed
cmd.Parameters.Add(sp);
to
cmd.Parameters.Add(sp.ParameterName, sp.Value);
If i create the following method concerning transaction :
public static int Insert(string processMethod, object[] processParameters, Type processType, object process, UserTransactionDTO transObj, string spPostConfirm, int toEmpNum,int confirmState)
{
int affectedRows = -7;
using (IfxConnection conn = new IfxConnection(ConfigurationManager.ConnectionStrings["crms"].ToString() + " Enlist=true;"))
{
if (conn.State == ConnectionState.Closed)
{
conn.Open();
}
using (IfxTransaction tran = conn.BeginTransaction())
{
if (!string.IsNullOrEmpty(processMethod))//business Method
{
processParameters[1] = conn;
processParameters[2] = tran;
MethodInfo theMethod = processType.GetMethod(processMethod, new[] { processParameters.First().GetType(), typeof(IfxConnection), typeof(IfxTransaction) });
object res = theMethod.Invoke(process, processParameters);
transObj.ValuesKey = res.ToString();
}
if (!string.IsNullOrEmpty(transObj.ValuesKey))
{
affectedRows = RunPreConfirm(transObj.TaskCode, transObj.UserStateCode, transObj.ValuesKey, conn, tran, confirmState);//sp_confirm
if (affectedRows != 1)
{
tran.Rollback();
tran.Dispose();//Dispose
conn.Close();
conn.Dispose();
return -1;//Fail
}
affectedRows = InsertTrans(transObj, conn, tran);//MainTransaction --->df2usertrans
if (affectedRows == 1)//Success
{
if (!string.IsNullOrEmpty(spPostConfirm))
{
affectedRows = RunPostConfirm(spPostConfirm, transObj.ValuesKey, conn, tran);//sp_post_confirm
if (affectedRows != 0)
{
tran.Rollback();
tran.Dispose();//Dispose
conn.Close();
conn.Dispose();
return -2;//Fail
}
}
affectedRows = RunAfterTrans(transObj.TaskCode, transObj.OldStatusCode, transObj, toEmpNum, conn, tran);//sp_after_trans
if (affectedRows != 1)
{
tran.Rollback();
tran.Dispose();//Dispose
conn.Close();
conn.Dispose();
return -3;//Fail
}
tran.Commit();
tran.Dispose();
conn.Close();
conn.Dispose();
return 1;
}
else
{
tran.Rollback();
tran.Dispose();//Dispose
conn.Close();
conn.Dispose();
return -1;//Fail
}
}
else
{
tran.Rollback();
tran.Dispose();//Dispose
conn.Close();
conn.Dispose();
return -1;//Fail
}
}
}
return affectedRows;
}
I want to ask three questions :
1-if one of my internal methods failed to insert before } Does the connection and the transaction disposed and closed automatically or not ?I mean should i call the following block of code :
tran.Dispose();
conn.Close();
conn.Dispose();
2-Could i invoke an instance method with its properties instead of fill the object and passing it as a parameter again ?
object res = theMethod.Invoke(process, processParameters);
I mean :
I want to use this(with its object state) because it's instance method:
public string InsertRequest(IfxConnection conn,IfxTransaction trans)
instead of this current method :
public string InsertRequest(EnhancementRequest obj, IfxConnection conn,IfxTransaction trans)
3-Is the following code written well? I mean, no redundant steps and no logical errors.?
The code has some redundancies and some possible issues.
First of all if you are narrowing scope of connection and transaction objects with using statement, you don't need to call Dispose on any of these objects as using will take care of it for you.
If your functions calling stored procedures are not handling database exceptions you should add try .. except aroung your transaction scope:
using (IfxTransaction tran = conn.BeginTransaction())
{
try
{
// All db operations here
tran.Commit();
}
catch(Exception e)
{
tran.Rollback();
throw; // Or return error code
}
}
So effectively if some non-exception validation condition fails you just need to call trans.Rollback() and return your error code.
Regarding your question 2 I would suggest adding generic call as your function parameter:
public static int Insert(Func<IfxConnection, IfxTransaction, object> callback)
{
// ...
object res = callback(conn, tran);
// ...
}
In the code above I used generic delegate Func, you can call your insert function like this:
Insert((conn, tran) =>
{
// do something here with conn and tran and return object
});
Alternatively you can declare your own delegate signature which can be helpful if you plan to change it in the future:
public delegate object MyDelegateType(IfxConnection conn, IfxTransaction tran);
Then when calling insert just pass the object and method of your choice as an argument:
public class SomeClass
{
public static object ProcessOnInsert(IfxConnection conn, IfxTransaction tran)
{
// do something here with conn and tran
}
public static int Insert(MyDelegateType callback)
// or
// public static int Insert(Func<IfxConnection,IfxTransaction,object> callback)
{
// ...
callback(conn, tran);
// ...
}
public static void RunInsert()
{
Insert(ProcessOnInsert);
}
}
The method after some changes:
public static int Insert(Func<IfxConnection, IfxTransaction, object> processMethod, UserTransactionDTO transObj, string spPostConfirm, int toEmpNum, int confirmState)
{
int affectedRows = -7;
using (IfxConnection conn = new IfxConnection(ConfigurationManager.ConnectionStrings["crms"].ToString() + " Enlist=true;"))
{
if (conn.State == ConnectionState.Closed)
{
conn.Open();
}
using (IfxTransaction tran = conn.BeginTransaction())
{
try
{
if (processMethod != null)//business Method
{
object res = processMethod(conn, tran);
transObj.ValuesKey = res.ToString();
}
if (string.IsNullOrEmpty(transObj.ValuesKey)) //Fail
{
tran.Rollback();
return -1;//Fail
}
affectedRows = RunPreConfirm(transObj.TaskCode, transObj.UserStateCode, transObj.ValuesKey, conn, tran, confirmState);//sp_confirm
if (affectedRows != 1)
{
tran.Rollback();
return -1;//Fail
}
affectedRows = InsertTrans(transObj, conn, tran);//MainTransaction --->df2usertrans
if (affectedRows != 1)//Fail
{
tran.Rollback();
return -1;//Fail
}
if (!string.IsNullOrEmpty(spPostConfirm))
{
affectedRows = RunPostConfirm(spPostConfirm, transObj.ValuesKey, conn, tran);//sp_post_confirm
if (affectedRows != 0)
{
tran.Rollback();
return -2;//Fail
}
}
affectedRows = RunAfterTrans(transObj.TaskCode, transObj.OldStatusCode, transObj, toEmpNum, conn, tran);//sp_after_trans
if (affectedRows != 1)
{
tran.Rollback();
return -3;//Fail
}
tran.Commit();
return 1;
}
catch
{
trans.Rollback();
throw;
}
}
}
return affectedRows;
}
When calling this function just pass any instance method with matching signature as processMethod.
Another suggestion is to change all functions to use the similar callback syntax and instead of passing string and object arguments, call the method directly as strong typing in this case gives more readability.
Regarding readability as you run of series of operations in a transaction and if either one fails, whole transaction needs to fail, it's better to reduce nested conditions and check for fail first (see above).
I have the below generic method that works very well in reading 1 resultset.I have been fiddling trying to write a generic method to return multiple resulset
Could you give me a hand or suggestion how I could turn the below one into a method that returns/handle multiple resultset.
usage:
const string spName = "GetCountry";
object[] parms = { "#CountryId", countryId };
myDto dto = spName.GetOne(ToDto, parms);
return model;
public static T GetOne<T>(this string spName,
Func<IDataReader, T> createObject,
object[] parms = null)
{
try
{
using (var connection = new SqlConnection(ConnectionString))
{
using (var command = new SqlCommand())
{
command.Connection = connection;
command.CommandType = CommandType.StoredProcedure;
command.CommandText = spName;
command.SetParameters(parms);
connection.Open();
T t = default(T);
var reader = command.ExecuteReader();
if (reader.Read())
{
t = createObject(reader);
}
return t;
}
}
}
catch (SqlException ex)
{
etc......
}
return default(T);
}
an example would be :A customer with Addresses (SP will return a customer all the related addresses by returning 2 resultsets inside a sp.)
Many thanks for any suggestions