Why my query doesn't update the table with my input parameters? - c#

I don't know why the following query doesn't executed with my expected parameters !!
cmdTxt.Append("UPDATE sd32depart SET currentcredit = currentcredit + ? WHERE year = ? AND main_code = ? ");
paramList.Add("currentcredit", value.ToString().TrimEnd());
paramList.Add("year", year.ToString().TrimEnd());
paramList.Add("main_code", main_code.ToString().TrimEnd());
res = ConnectionObj.Execute_NonQueryWithTransaction(cmdTxt.ToString(), CommandType.Text, paramList);
I get
res = 1and although currentcredit = 180 as a parameter
when i check my table i found currentcredit NULL !!
public int Execute_NonQueryWithTransaction(string cmdText)
{
string return_msg = "";
int return_val = -1;
//check if connection closed then return -1;
if (connectionstate == ConnectionState.Closed)
return -1;
command.CommandText = cmdText;
command.CommandType = CommandType.Text;
command.Transaction = current_trans;
try
{
return_val = command.ExecuteNonQuery();
}
catch (IfxException ifxEx)// Handle IBM.data.informix : mostly catched
{
return_val = ifxEx.Errors[0].NativeError;
return_msg = return_val.ToString();
}
catch (Exception ex)// Handle all other exceptions.
{
return_msg = ex.Message;
}
finally
{
if (!string.IsNullOrEmpty(return_msg))//catch error
{
//rollback
current_trans.Rollback();
Close_Connection();
}
}
return return_val;
}

From the comments:
currentcredit is null before the update, what should i do
Ah, that's the problem then. In SQL, null is sticky. null + anything is: null. If this was TSQL (i.e. SQL Server), the solution would be ISNULL:
UPDATE sd32depart SET currentcredit = ISNULL(currentcredit,0) + ?
where the result of ISNULL(x, y) is x if x is non-null, otherwise y. In C# terms, it is the equivalent of x ?? y (and indeed, ISNULL(x, y) is identical to COALESCE(x, y), except that COALESCE is varadic).
So: find the informix equivalent of ISNULL or COALESCE, and use that.
From a brief search, it seems that in informix the NVL function does this, so try:
UPDATE sd32depart SET currentcredit = NVL(currentcredit,0) + ?

Related

Defensive opening of a database connection with option to retry

Occasionally I am getting a SqlException exception on the line marked below.
It occurs when we have network problems and the server cannot be found.
public void markComplete(int aTKey)
{
SqlCommand mySqlCommand = null;
SqlConnection myConnect = null;
mySqlCommand = new SqlCommand();
try
{
myConnect = new SqlConnection(ConfigurationManager.ConnectionStrings["foo"].ConnectionString);
mySqlCommand.Connection = myConnect;
mySqlCommand.Connection.Open(); //<<<<<<<< EXCEPTION HERE <<<<<<<<<<<<<<<<<<
mySqlCommand.CommandType = CommandType.Text;
mySqlCommand.CommandText =
" UPDATE dbo.tb_bar " +
" SET LastUpdateTime = CONVERT(Time,GETDATE()) " +
" WHERE AKey = " + aTKey;
mySqlCommand.ExecuteNonQuery();
mySqlCommand.Connection.Close();
}
finally
{
if(mySqlCommand != null)
{
mySqlCommand.Dispose();
}
}
}
I have two questions so maybe should split into 2 SO questions:
Is my finally statement sufficiently defensive ?
Rather than just failing how involved would it be to amend the method so instead of crashing it waits for say 10 minutes and then tries again to open the connection - tries a maximum of 3 times and if still no valid connection it moves on?
Use parameters. Do not concatenate strings to create SQL statements. Read about SQL Injection.
Instead of using try...finally you can simplify your code with the using statement. You need to dispose all the instances that implements the IDisposable interface, so you also need to use the using statement with the SqlConnection. In fact, it's even more important then disposing the SqlCommand, since it allows ADO.Net to use connection pooling.
You don't need to do the entire procedure again and again, just the connection.Open and the ExecuteNonQuery.
Using the constructor that accepts a string an SqlConnection saves you the need to set them via properties.
You don't need to specify CommandType.Text - it's the default value.
Here is a basic implementation of retry logic with some improvements to your code:
public void markComplete(int aTKey)
{
var sql = " UPDATE dbo.tb_bar " +
" SET LastUpdateTime = CONVERT(Time,GETDATE()) " +
" WHERE AKey = #aTKey";
using(var myConnect = new SqlConnection(ConfigurationManager.ConnectionStrings["foo"].ConnectionString))
{
using(var mySqlCommand = new SqlCommand(sql, myConnect))
{
mySqlCommand.Parameters.Add("#aTKey", SqlDbType.Int).Value = aTKey;
var success = false;
var attempts = 0;
do
{
attempts++;
try
{
mySqlCommand.Connection.Open();
mySqlCommand.ExecuteNonQuery();
success = true;
}
catch(Exception ex)
{
// Log exception here
Threading.Thread.Sleep(1000);
}
}while(attempts < 3 || !success);
}
}
}
Update:
Well, I've had some free time and I remember writing a general retry method a few years back. Couldn't find it but here is the general idea:
static void RetryOnException(Action action, int numberOfRetries, int timeoutBetweenRetries)
{
var success = false;
var exceptions = new List<Exception>();
var currentAttempt = 0;
do
{
currentAttempt++;
try
{
action();
success = true;
}
catch(Exception ex)
{
exceptions.Add(ex);
Threading.Thread.Sleep(timeoutBetweenRetries);
}
} while(!success || currentAttempt < numberOfRetries);
// Note: The Exception will only be thrown in case all retries fails.
// If the action completes without throwing an exception at any point, all exceptions before will be swallowed by this method. You might want to log them for future analysis.
if(!success && exceptions.Count > 0)
{
throw new AggregateException("Failed all {numberOfRetries} retries.", exceptions);
}
}
Using this method you can retry all sort of things, while keeping your methods simpler and cleaner.
So here is how it should be used:
public void markComplete(int aTKey)
{
var sql = " UPDATE dbo.tb_bar " +
" SET LastUpdateTime = CONVERT(Time,GETDATE()) " +
" WHERE AKey = #aTKey";
using(var myConnect = new SqlConnection(ConfigurationManager.ConnectionStrings["foo"].ConnectionString))
{
using(var mySqlCommand = new SqlCommand(sql, myConnect))
{
mySqlCommand.Parameters.Add("#aTKey", SqlDbType.Int).Value = aTKey;
// You can do this inside a `try...catch` block or let the AggregateException propagate to the calling method
RetryOnException(
() => {
mySqlCommand.Connection.Open();
mySqlCommand.ExecuteNonQuery();
}, 3, 1000);
}
}
}

c# odbcDatareader get the duplicated field value

I have a strange problem:
Database: Firebird
Connection String:
Driver={Firebird/InterBase(r)driver};Dbname=xxx;CHARSET=NONE;UID=xxx;
PASSWORD=xxx
I use a set of ODBC classes to operation(select) the database table
when I loop the db records with OdbcDataReader.GetValue(), if some fields(char type) have no value(char_length()=0), it would get the last record field value; if fields has the value, it's ok(does not get the value from last record)
My code likes below:
var dr = this.SqlExecutor.Open(sql); //sql is String variable that stored the sql statement
while (dr.Read())
{
this.Logger.Info("-----Customer_Id: " + this.SqlReader.GetFieldAsString(dr, "Customer_Id") + " -----"); // this not duplicated because it's not empty
this.Logger.Info("-----Customer_Email: " + this.SqlReader.GetFieldAsString(dr, "Customer_email") + " -----"); //this would if some records has empty value
}
// the method SqlExecutor.Open(sql) and SqlReader.GetFieldAsString() please refer to below:
public IDataReader Open(string sql)
{
this.Logger.Debug("sql: " + sql);
if (this.reader != null && !this.reader.IsClosed)
{
this.reader.Close();
this.reader = null;
}
try
{
this.cmdForSelect.Connection = this.conn;
this.cmdForSelect.CommandTimeout = 120;
this.cmdForSelect.CommandText = sql;
this.cmdForSelect.Parameters.Clear();
foreach (var p in this.dbParameters)
{
this.cmdForSelect.Parameters.Add(p);
}
this.reader = cmdForSelect.ExecuteReader();
}
catch (Exception ex)
{
this.Logger.Error("There is an error: {0}", ex.Message);
this.Logger.Info("Error sql query:" + sql);
throw;
}
finally
{
this.ClearParameters();
}
return this.reader;
}
public string GetFieldAsString(IDataReader dr, string fieldName)
{
try
{
var value = dr.GetValue(dr.GetOrdinal(fieldName));
if (value == DBNull.Value)
{
return string.Empty;
}
return Convert.ToString(value);
}
catch
{
return string.Empty;
}
}
Besides, this case is fine on my computer, just happened on my customer's computer, I feel this does not matter with mycode, anyone know this, please help me, thanks a lot!!!
Assuming the actual code is not posted in your question; I think the problem is with re-initiation of the variable in the actual code. You need to revisit the code and check the IF-ELSE condition you have applied on the this.SqlReader.GetFieldAsString(dr, "Customer_email")

Parameter '?user_email' not found in the collection

I am using MySql 5.6x with Visual Studio 2015, windows 10, 64-bit. C# as programming language. In my CRUD.cs (Class file) i have created the following method:
public bool dbQuery(string sql,string[] paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null){
foreach(string i in paramList){
string[] valus = i.Split(',');
string p = valus[0];
string v = valus[1];
cmd.Parameters[p].Value = v;
}
}
if (cmd.ExecuteNonQuery() > 0)
{
flag = true;
}
}
catch (Exception exc)
{
error(exc);
}
}
I am passing the query and Parameters List like this:
protected void loginBtn_Click(object sender, EventArgs e)
{
string sql = "SELECT * FROM dept_login WHERE (user_email = ?user_email OR user_cell = ?user_cell) AND userkey = ?userkey";
string[] param = new string[] {
"?user_email,"+ userid.Text.ToString(),
"?user_cell,"+ userid.Text.ToString(),
"?userkey,"+ userkey.Text.ToString()
};
if (db.dbQuery(sql, param))
{
msg.Text = "Ok";
}
else
{
msg.Text = "<strong class='text-danger'>Authentication Failed</strong>";
}
}
Now the problem is that after the loop iteration complete, it directly jumps to the catch() Block and generate an Exception that:
Parameter '?user_email' not found in the collection.
Am i doing this correct to send params like that? is there any other way to do the same?
Thanks
EDIT: I think the best way might be the two-dimensional array to collect the parameters and their values and loop then within the method to fetch the parameters in cmd.AddWidthValues()? I may be wrong...
In your dbQuery you don't create the parameters collection with the expected names, so you get the error when you try to set a value for a parameter that doesn't exist
public bool dbQuery(string sql,string[] paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null){
foreach(string i in paramList){
string[] valus = i.Split(',');
string p = valus[0];
string v = valus[1];
cmd.Parameters.AddWithValue(p, v);
}
}
if (cmd.ExecuteNonQuery() > 0)
flag = true;
}
catch (Exception exc)
{
error(exc);
}
}
Of course this will add every parameter with a datatype equals to a string and thus is very prone to errors if your datatable columns are not of string type
A better approach would be this one
List<MySqlParameter> parameters = new List<MySqlParameter>()
{
{new MySqlParameter()
{
ParameterName = "?user_mail",
MySqlDbType= MySqlDbType.VarChar,
Value = userid.Text
},
{new MySqlParameter()
{
ParameterName = "?user_cell",
MySqlDbType= MySqlDbType.VarChar,
Value = userid.Text
},
{new MySqlParameter()
{
ParameterName = "?userkey",
MySqlDbType = MySqlDbType.VarChar,
Value = userkey.Text
},
}
if (db.dbQuery(sql, parameters))
....
and in dbQuery receive the list adding it to the parameters collection
public bool dbQuery(string sql, List<MySqlParameter> paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null)
cmd.Parameters.AddRange(paramList.ToArray());
if (cmd.ExecuteNonQuery() > 0)
{
flag = true;
}
}
catch (Exception exc)
{
error(exc);
}
}
By the way, unrelated to your actual problem, but your code doesn't seem to close and dispose the connection. This will lead to very nasty problems to diagnose and fix. Try to use the using statement and avoid a global connection variable
EDIT
As you have noticed the ExecuteNonQuery doesn't work with a SELECT statement, you need to use ExecuteReader and check if you get some return value
using(MySqlDataReader reader = cmd.ExecuteReader())
{
flag = reader.HasRows;
}
This, of course, means that you will get troubles when you want to insert, update or delete record where instead you need the ExecuteNonQuery. Creating a general purpose function to handle different kind of query is very difficult and doesn't worth the work and debug required. Better use some kind of well know ORM software like EntityFramework or Dapper.
Your SQL Commands' Parameters collection does not contain those parameters, so you cannot index them in this manner:
cmd.Parameters[p].Value = v;
You need to add them to the Commands' Parameters collection in this manner: cmd.Parameters.AddWithValue(p, v);.

Should I refactor this, or is my confusion cause to be cautious? [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
This SqlCe code looks awfully strange to me:
cmd.CommandText = "INSERT INTO departments ( account_id, name) VALUES (?, ?)";
foreach(DataTable tab in dset.Tables)
{
if (tab.TableName == "Departments")
{
foreach(DataRow row in tab.Rows)
{
Department Dept = new Department();
if (!ret)
ret = true;
foreach(DataColumn column in tab.Columns)
{
if (column.ColumnName == "AccountID")
{
Dept.AccountID = (string) row[column];
}
else if (column.ColumnName == "Name")
{
if (!row.IsNull(column))
Dept.AccountName = (string) row[column];
else
Dept.AccountName = "";
}
}
List.List.Add(Dept);
. . .
dSQL = "INSERT INTO departments ( account_id, name) VALUES ('" + Dept.AccountID + "','" + Dept.AccountName +"')";
if (!First)
{
cmd.Parameters[0].Value = Dept.AccountID;
cmd.Parameters[1].Value = Dept.AccountName;
}
if (First)
{
cmd.Parameters.Add("#account_id",Dept.AccountID);
cmd.Parameters.Add("name",Dept.AccountName);
cmd.Prepare();
First = false;
}
if (frmCentral.CancelFetchInvDataInProgress)
{
ret = false;
return ret;
}
try
{
dbconn.DBCommand( cmd, dSQL, true );
}
. . .
public void DBCommand(SqlCeCommand cmd, string dynSQL, bool Silent)
{
SqlCeTransaction trans = GetConnection().BeginTransaction();
cmd.Transaction = trans;
try
{
cmd.ExecuteNonQuery();
trans.Commit();
}
catch (Exception ex)
{
try
{
trans.Rollback();
}
catch (SqlCeException)
{
// Handle possible exception here
}
MessageBox.Show("DBCommand Except 2"); // This one I haven't seen...
WriteDBCommandException(dynSQL, ex, Silent);
}
}
My questions are:
1) Should "?" really be used in the assignment to cmd.CommandText, or should "#" be used instead?
2) One of the "cmd.Parameters.Add()"s (account_id) uses a "#" and the other (name) doesn't. Which way is right, or is the "#" optional?
3) I can't make heads or tails of why DBCommand() is written as it is - the final two args are only used if there's an exception...???
I'm tempted to radically refactor this code, because it seems so bizarre, but since I don't really understand it, that might be a recipe for disaster...
I'm fairly certain this article will answer some of your questions:
http://msdn.microsoft.com/en-us/library/yy6y35y8.aspx
The second chart explains the difference between the named and positional (?) parameters (used in OleDb and ODBC).
I believe in the case where the ? is used, the # is optional, but I'm not sure of this. If it's working, I'd say that that IS the case.
The stuff in DBCommand appears to simply be there for logging purposes. If the exection fails, it tries to do a rollback and then logs the exception with the sql command (in dynSQL).
The ? parameter is older Access syntax.
My guess is this used to be an Access database, but someone converted it to SQL CE at some point.
Generally, SQL understands that ? parameter, but it's better to just change that while you are in there so that it is more understood.
I'm still trying to make heads & tails of all these variables. If I get it sorted out, I'll post up compilable (sp?) code.
EDIT: I had to put this into a method and work out all of the RED errors to make sure I wasn't giving you something that would not compile.
I passed it your DataSet like so, with lots of comments added:
private bool StrangeSqlCeCode(DataSet dset) {
const string ACCOUNT_ID = "AccountID";
const string DEPARTMENTS = "Departments";
const string NAME = "Name";
const string SQL_TEXT = "INSERT INTO departments (account_id, name) VALUES (#account_id, #name)";
bool ret = false;
//bool First = false; (we don't need this anymore, because we initialize the SqlCeCommand correctly up front)
using (SqlCeCommand cmd = new SqlCeCommand(SQL_TEXT)) {
// Be sure to set this to the data type of the database and size field
cmd.Parameters.Add("#account_id", SqlDbType.NVarChar, 100);
cmd.Parameters.Add("#name", SqlDbType.NVarChar, 100);
if (-1 < dset.Tables.IndexOf(DEPARTMENTS)) {
DataTable tab = dset.Tables[DEPARTMENTS];
foreach (DataRow row in tab.Rows) {
// Check this much earlier. No need in doing all the rest if a Cancel has been requested
if (!frmCentral.CancelFetchInvDataInProgress) {
Department Dept = new Department();
if (!ret)
ret = true;
// Wow! Long way about getting the data below:
//foreach (DataColumn column in tab.Columns) {
// if (column.ColumnName == "AccountID") {
// Dept.AccountID = (string)row[column];
// } else if (column.ColumnName == "Name") {
// Dept.AccountName = !row.IsNull(column) ? row[column].ToString() : String.Empty;
// }
//}
if (-1 < tab.Columns.IndexOf(ACCOUNT_ID)) {
Dept.AccountID = row[ACCOUNT_ID].ToString();
}
if (-1 < tab.Columns.IndexOf(NAME)) {
Dept.AccountName = row[NAME].ToString();
}
List.List.Add(Dept);
// This statement below is logically the same as cmd.CommandText, so just don't use it
//string dSQL = "INSERT INTO departments ( account_id, name) VALUES ('" + Dept.AccountID + "','" + Dept.AccountName + "')";
cmd.Parameters["#account_id"].Value = Dept.AccountID;
cmd.Parameters["#name"].Value = Dept.AccountName;
cmd.Prepare(); // I really don't ever use this. Is it necessary? Perhaps.
// This whole routine below is already in a Try/Catch, so this one isn't necessary
//try {
dbconn.DBCommand(cmd, true);
//} catch {
//}
} else {
ret = false;
return ret;
}
}
}
}
return ret;
}
I wrote an overload for your DBCommand method to work with Legacy code:
public void DBCommand(SqlCeCommand cmd, string dynSQL, bool Silent) {
cmd.CommandText = dynSQL;
DBCommand(cmd, Silent);
}
public void DBCommand(SqlCeCommand cmd, bool Silent) {
string dynSQL = cmd.CommandText;
SqlCeTransaction trans = GetConnection().BeginTransaction();
cmd.Transaction = trans;
try {
cmd.ExecuteNonQuery();
trans.Commit();
} catch (Exception ex) {
try {
trans.Rollback(); // I was under the impression you never needed to call this.
// If Commit is never called, the transaction is automatically rolled back.
} catch (SqlCeException) {
// Handle possible exception here
}
MessageBox.Show("DBCommand Except 2"); // This one I haven't seen...
//WriteDBCommandException(dynSQL, ex, Silent);
}
}

Dialect/Driver - Every SELECT I perform, add with(nolock)

I need to know a way to implement in my system, a Driver or Dialect which, whenever I perform a SELECT in Nhibernate, the SELECT adds the with(nolock) with it.
I need that to be in C# and NHibernate, not directly in the DB !
Hope you can understand !
Thanks !
It is possible to modify the sql using an Interceptor and overriding the OnPrepareStatement method, something like this:
public class AddNoLockHintsInterceptor : EmptyInterceptor
{
public override SqlString OnPrepareStatement(SqlString sql)
{
// Modify the sql to add hints
return sql;
}
}
And here is a way to register the interceptor with NHibernate:
var session = SessionFactory.OpenSession(new AddNoLockHintsInterceptor());
Using the WITH(NOLOCK) hint is the same thing as using a READ UNCOMMITED transaction isolation level as discussed here: When should you use "with (nolock)".
You can specify the transaction isolation level when starting new transactions with NHibernate:
var session = SessionFactory.OpenSession();
session.BeginTransaction(IsolationLevel.ReadUncommitted);
I would not recommend this unless you really know what you are doing though. Here's some more information on the subject: Why use a READ UNCOMMITTED isolation level?.
Hope this would help someone,
I used this code to add no lock hints to the most of the queries, related to the answer dillenmeister
public class NoLockHintsInterceptor : EmptyInterceptor
{
public override SqlString OnPrepareStatement(SqlString sql)
{
// Modify the sql to add hints
if (sql.StartsWithCaseInsensitive("select"))
{
var parts = new List<object>((object[]) sql.Parts);
object fromItem = parts.FirstOrDefault(p => p.ToString().ToLower().Trim().Equals("from"));
int fromIndex = fromItem != null ? parts.IndexOf(fromItem) : -1;
object whereItem = parts.FirstOrDefault(p => p.ToString().ToLower().Trim().Equals("where"));
int whereIndex = whereItem != null ? parts.IndexOf(whereItem) : parts.Count;
if (fromIndex == -1)
return sql;
parts.Insert(parts.IndexOf(fromItem) + 2, " with(nolock) ");
for (int i = fromIndex; i < whereIndex; i++)
{
if (parts[i - 1].Equals(","))
{
parts.Insert(i + 2, " with(nolock) ");
i += 2;
}
if (parts[i].ToString().Trim().EndsWith(" on"))
{
parts[i] = parts[i].ToString().Replace(" on", " with(nolock) on ");
}
}
sql = new SqlString(parts.ToArray());
}
return sql;
}
}
There are two errors in this code:
For SQL script that has parameter This code won't work.
SqlString.Parts is not compile, I am using NHibernate 4.0.0.4000
Here is the Fix:
public class NoLockInterceptor : EmptyInterceptor
{
public override SqlString OnPrepareStatement(SqlString sql)
{
//var log = new StringBuilder();
//log.Append(sql.ToString());
//log.AppendLine();
// Modify the sql to add hints
if (sql.StartsWithCaseInsensitive("select"))
{
var parts = sql.ToString().Split().ToList();
var fromItem = parts.FirstOrDefault(p => p.Trim().Equals("from", StringComparison.OrdinalIgnoreCase));
int fromIndex = fromItem != null ? parts.IndexOf(fromItem) : -1;
var whereItem = parts.FirstOrDefault(p => p.Trim().Equals("where", StringComparison.OrdinalIgnoreCase));
int whereIndex = whereItem != null ? parts.IndexOf(whereItem) : parts.Count;
if (fromIndex == -1)
return sql;
parts.Insert(parts.IndexOf(fromItem) + 3, "WITH (NOLOCK)");
for (int i = fromIndex; i < whereIndex; i++)
{
if (parts[i - 1].Equals(","))
{
parts.Insert(i + 3, "WITH (NOLOCK)");
i += 3;
}
if (parts[i].Trim().Equals("on", StringComparison.OrdinalIgnoreCase))
{
parts[i] = "WITH (NOLOCK) on";
}
}
// MUST use SqlString.Parse() method instead of new SqlString()
sql = SqlString.Parse(string.Join(" ", parts));
}
//log.Append(sql);
return sql;
}
}

Categories

Resources