Is this the optimal way to guard against failed DB commits? - c#

I have a legacy method that keeps throwing an exception. It has a nested try|catch. Is this the best way to code that sort of thing:
public void DBCommand(string dynSQL, bool Silent)
{
checkConnection(); //Despite the name, this "returns" void, not bool
SqlCeCommand cmd = objCon.CreateCommand();
SqlCeTransaction trans = GetConnection().BeginTransaction();
cmd.Transaction = trans;
try
{
cmd.CommandText = dynSQL;
cmd.ExecuteNonQuery();
trans.Commit();
}
catch (Exception ex)
{
try
{
trans.Rollback();
}
catch (SqlCeException sqlceex)
{
MessageBox.Show(string.Format("SqlCeException ({0})", sqlceex.Message));
CCR.LogMsgs.Append(string.Format("SqlCeException exception: {0}\r\n", sqlceex.Message));
// Handle possible Rollback exception here
}
MessageBox.Show(string.Format("DBCommand Except ({0})", ex.Message));
CCR.LogMsgs.Append(string.Format("DBCommand exception: {0}\r\n", ex.Message));
}
}
?
I want to refactor this to use using statements for at least the SqlCeCommand, but for now the above is the "as-is" code. I'm seeing the general exception message
("DBCommand Except"), never the "SqlCeException"
UPDATE
By adding some MessageBox.Show() calls back (debug log file no longer being written for some reason), I found that this is the DDL that throws the exception:
ALTER TABLE CCR032713190114 ADD salvationId nvarchar(19), salvation float
Note: "CCR032713190114" has been proven to be a valid tablename (it exists) at this point in thecode.
Is there something wrong with this DDL that would cause a problem?
UPDATE 2
I changed the code from this:
ddl = string.Format("ALTER TABLE {0} ADD salvationID nvarchar(19) ", tablename);
dbconn.DBCommand(ddl,false);
ddl = string.Format("UPDATE {0} SET salvationID = {1}", tablename, string.Empty);
...to this:
ddl = string.Format("ALTER TABLE {0} ADD salvationID nvarchar(19) NOT NULL WITH DEFAULT", tablename);
dbconn.DBCommand(ddl,false);
...but now, right after "ALTER TABLE BLA ADD salvation float NOT NULL WITH DEFAULT" I'm seeing this err msg, "DBCommand Except (There was an error parsing the query.
[Token line number, Token line offset,, Token in error,,])"
What in Azure braziers is going on here?
Do I need to specify a default val after the "WITH DEFAULT" (won't '' or string.empty automatically be the default for a nvarchar column, 0.0 for a float, etc.)?

You cannot add two columns in same ALTER TABLE statement, must add one at a time, also good idea to specify NULL or NOT NULL (if NOT NULL, a DEFAULT may be required)

If you do not ever see the SqlCeException then it would truly be exceptional. To keep it 'out of the way', I would do the following:
public void DBCommand(string dynSQL, bool Silent) {
checkConnection(); //Despite the name, this "returns" void, not bool
SqlCeCommand cmd = objCon.CreateCommand();
SqlCeTransaction trans = GetConnection().BeginTransaction();
cmd.Transaction = trans;
var doRollback = false;
try {
cmd.CommandText = dynSQL;
cmd.ExecuteNonQuery();
trans.Commit();
}
catch (Exception ex) {
doRollback = true
MessageBox.Show(string.Format("DBCommand Except ({0})", ex.Message));
CCR.LogMsgs.Append(string.Format("DBCommand exception: {0}\r\n", ex.Message));
}
finally {
if(doRollback) }
DoRollback();
}
}
}
void DoRollback(){
try {
trans.Rollback();
}
catch (SqlCeException sqlceex) {
MessageBox.Show(string.Format("SqlCeException ({0})", sqlceex.Message));
CCR.LogMsgs.Append(string.Format("SqlCeException exception: {0}\r\n", sqlceex.Message));
// Handle possible Rollback exception here
}
}
}

Related

Retrieving SQL error message from transaction.Commit()

I have a piece of code that iterates through a list of SQL commands that should be processed all as part of one transaction. I want to have a way of knowing if that transaction was successful for error logging and handling purposes. At this moment in time for some reason I have a problem seeing any kind of actual exception or unsuccessful commit. I am using the code below right now. The try catch is from the MSDN page recommendation. Please feel free to poke any other holes in this that you can see that are not 100% about my question. These are all commands of SqlCommands of the stored proc type with parameters added to it prior to adding it to the list of SQL commands.
public static async Task UpdateSQL(string inputQuery,
string inputId, List<SqlCommand> commandList, TraceWriter log)
{
try
{
string str = ConfigurationManager.ConnectionStrings
["connString"].ConnectionString;
log.Info($"Running query: {inputQuery}");
int commandRows = 0;
using (SqlConnection conn = new SqlConnection(str))
{
conn.Open();
SqlTransaction tr = conn.BeginTransaction();
try
{
SqlCommand cmd = new SqlCommand(inputQuery, conn);
cmd.Transaction = tr;
foreach (var command in commandList)
{
command.Connection = conn;
command.Transaction = tr;
log.Info($"{command.CommandText} query running"); //last log output if unsuccesful (based on end result in database)
commandRows += await command.ExecuteNonQueryAsync();
}
log.Info($"total rows updated: {commandRows}");
tr.Commit();
conn.Close();
}
catch(Exception ex)
{
Console.WriteLine($"Commit Exception Type: {ex.GetType()}");
Console.WriteLine($" Message: {ex.Message}");
try
{
tr.Rollback();
conn.Close();
log.Info($"{inputId} transaction rolled back");
}
catch (Exception ex2)
{
// rollback fail Exception
log.Info($"Rollback Exception Type: {ex2.GetType()}");
log.Info($" Message: {ex2.Message}");
conn.Close();
}
}
}
}
Try and catch SqlException
Then do this:
StringBuilder sqlErrorMessages = new StringBuilder("Sql Exception:\n");
foreach (SqlError error in ex.Errors)
{
sqlErrorMessages.AppendFormat("Mesage: {0}\n", error.Message)
.AppendFormat("Severity level: {0}\n", error.Class)
.AppendFormat("State: {0}\n", error.State)
.AppendFormat("Number: {0}\n", error.Number)
.AppendFormat("Procedure: {0}\n", error.Procedure)
.AppendFormat("Source: {0}\n", error.Source)
.AppendFormat("LineNumber: {0}\n", error.LineNumber)
.AppendFormat("Server: {0}\n", error.Server)
.AppendLine(new string('-',error.Message.Length+7));
}
You'll only get an exception in C# if your error's severity is 16 or above. If you are using a PRINT, you won't get an exception in .NET.
If you can edit the raise error code, this would cause a SqlException in C#:
RAISERROR('Some error message', 16, 1)
You can then get to each individual error in the SqlException.Errors collection.
Just a side-note - SQL Server will continue to run commands after the RAISERROR if you don't RETURN directly afterwards. If you don't return, you can get multiple errors back.

MVC SQL connection initialization

I am working on a MVC web page that edits a SQL DB table. In my controller, I have a DB call to increment an entity table. Then if successful, creates a new row in my target table (not the entity table).
The problem I am running into is I keep getting the following error:
The ConnectionString property has not been initialized.
However this only happens after the entity table has been incremented. Not sure where to go on this, so I am hoping that by posting some code, someone would be able to help me find my error.
so here is the obligatory code:
My SQL Connection:
private SqlConnection con;
public BaseRepository()
{
con = new SqlConnection(ConfigurationManager.ConnectionStrings["SqlServerConnection"].ToString());
}
My Entity Table Increment Call:
public int GetNextId()
{
try
{
using (con)
{
DynamicParameters dynParam= new DynamicParameters();
dynParam.Add("#entity_name", "insert_object ");
con.Open();
var value = con.Execute(SP_GET_NEW_ID, dynParam, commandType: CommandType.StoredProcedure);
con.Close();
return value;
}
}
catch (Exception ex) { throw ex; }
}
Finally, here is the Row Insert Code:
public int InsertRowCode(InsertObject ccModel, UserModel appUser)
{
var value = GetNextId();
if (value == 1)
{
try
{
using (con)
//this is where the code breaks and jumps the the exception ex in my catch
{
con.Open();
var dP = new DynamicParameters();
//(add 14 dynamic Parameters here)
var result = con.Execute(SP_SAVE_CORRECTION_CODES, dP, commandType: CommandType.StoredProcedure);
con.Close();
return result;
}
}
catch (Exception ex)
{
throw ex;
}
}
else { throw new Exception("Busted"); }
}
Any help is greatly appreciated. TIA
Don't use shared connection objects.
When you exit this block:
using (con)
{
//...
}
That connection object is now disposed and can't be used anymore. Don't worry about trying to optimize your connections, the connection pool does a very good job of that already. Create your connection objects where you need them, use them, and dispose them in a tight scope:
using (var con = new SqlConnection(connectionString))
{
//...
}
As a side note, this is superfluous:
catch (Exception ex)
{
throw ex;
}
That catch block isn't doing anything for you, and is actually deleting important information about the exception. Just remove that try/catch entirely.
If, on the other hand, you ever do want to do something with an exception before re-throwing it, just use the keyword throw by itself:
catch (Exception ex)
{
// log something, etc.
throw;
}
This would allow the exception to continue up the stack unmodified, preserving the actual error information.

Testing Sql Connection Validity

I am trying to write a method that will check if a database connection is valid or not. Here is the code behind my Test Connection button.
private void TestConn_btn_Click(object sender, EventArgs e)
{
DbConnection DBConnTest;
if (DataSrc_cbx.SelectedIndex == 1)
{
DBConnTest = new SqlConnection("Server="+DatabaseAddress_Value+"; Database="+DatabaseName_Value+";Trusted_Connection=true");
try
{
DBConnTest.Open();
MessageBox.Show("\nTEST SUCCESSFUL\n");
}
catch (Exception exception)
{
MessageBox.Show("TEST FAILED Exception Thrown: " + exception.Message);
}
finally
{
DBConnTest.Close();
}
}
}
The problem is that there is no exception thrown when I enter an invalid Database address ( or leave it empty all together), same applies to the Database name. It only throws an exception when there is no connection string, or in an incorrect format. So my question is, How do I make it check if there is indeed a server and a database on that server with the names input?
You can apply validations on your Web Page if the fields are empty then prompt user to enter something. Now use this statement to check whether this database exist or not??
select name from sys.sysdatabases
where dbid=db_id()
for user you need to ..
SELECT id FROM user WHERE login="JohnSmith"
and see if it gives you any rows. If yes - user exists.
You can use this work-around.
You need to execute a query to connect to the database.
For SQL Server, I usually use IDbCommand.ExecuteScalar to execute:
SELECT ##VERSION
For Oracle:
SELECT banner from v$version where banner like 'Oracle%'
Would you provide the complete code, please?
It would be something like:
try
{
using(SqlConnection conn = ...)
{
conn.Open();
using(SqlCommand command = conn.CreateCommand())
{
command.CommandText = "SELECT ##VERSION";
var result = (string) command.ExecuteScalar();
MessageBox.Show("\nTEST SUCCESSFUL\n" + result);
}
}
}
catch(Exception ex)
{
MessageBox.Show("TEST FAILED Exception Thrown: " + exception.Message);
}
Your code is working for me. The issue here is that you have to wait till the SQL timeout period elapses before the exception is thrown. This will not be a method that returns an immediate answer. If you wrap this try/catch with a WaitCursor, you will at least see when the code is running.
private void TestConn_btn_Click(object sender, EventArgs e)
{
this.Cursor = Cursors.WaitCursor;
DbConnection DBConnTest;
if (DataSrc_cbx.SelectedIndex == 1)
{
DBConnTest = new SqlConnection("Server="+DatabaseAddress_Value+"; Database="+DatabaseName_Value+";Trusted_Connection=true");
try
{
DBConnTest.Open();
MessageBox.Show("\nTEST SUCCESSFUL\n");
}
catch (Exception exception)
{
MessageBox.Show("TEST FAILED Exception Thrown: " + exception.Message);
}
finally
{
DBConnTest.Close();
}
}
this.Cursor = Cursors.Default;
}
Perhaps try:
using (SqlConnection conn = new SqlConnection(builder.ToString()))
{
try
{
conn.Open();
}
catch (SqlException ex)
{
foreach (SqlError error in ex.Errors)
{
Console.WriteLine(error.Number);
}
}
catch (Exception ex)
{
}
}
It will return the DB error code (run the following query for a list of error codes:
select * from sys.messages where language_id=1033 and severity between 11 and 16
Microsoft also provide some guidance here:
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlerror.number(v=vs.110).aspx
Your code looks incomplete!
Take this example from Microsoft.conexão c# com sql server 2008
Good luck!

SqlException The INSERT statement conflicted with the FOREIGN KEY constraint

The INSERT statement conflicted with the FOREIGN KEY constraint FK_Kupovina_Kupac. The conflict occurred in database OnlineApoteka, table dbo.Kupac, column 'ID'.
The statement has been terminated.
I'm really confused I do not know why we do not allow? I reported a bug, in throw ex; ?
public static void Kupi(long lekID, int kolicina, double cena, long nacinIsporukeID, string korisnickoIme)
{
SqlConnection con = new SqlConnection();
try
{
con.ConnectionString = ConfigurationManager.ConnectionStrings["OnlineApotekaConnectionString"].ConnectionString;
con.Open();
string updateLager = #"
UPDATE Lager
SET Kolicina=Kolicina-#Kolicina
WHERE LekID=#LekID";
SqlCommand cmd = new SqlCommand(updateLager, con);
cmd.Parameters.AddWithValue("#LekID", lekID);
cmd.Parameters.AddWithValue("#Kolicina", kolicina);
cmd.ExecuteNonQuery();
string insertIntoKupovina=#"
INSERT INTO Kupovina (KupacID, LekID, Datum, Kolicina, Cena, NacinIsporukeID)
VALUES (#KupacID, #LekID, #Datum, #Kolicina, #Cena, #NacinIsporukeID)";
cmd = new SqlCommand(insertIntoKupovina, con);
cmd.Parameters.AddWithValue("#KupacID", KupacAdapter.GetID(korisnickoIme));
cmd.Parameters.AddWithValue("#LekID", lekID);
cmd.Parameters.AddWithValue("#Datum", DateTime.Now.Date);
cmd.Parameters.AddWithValue("#Kolicina", kolicina);
cmd.Parameters.AddWithValue("#Cena", cena);
cmd.Parameters.AddWithValue("#NacinIsporukeID", nacinIsporukeID);
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
throw ex;
}
finally
{
con.Close();
}
}
I have a routine event for the button Buy.
When you click Kupi, should be removed from the table Lager volumes and placed in the Kupac Kupovina
protected void kupiButton_Click(object sender, EventArgs e)
{
KupovinaAdapter.Kupi(Convert.ToInt64(kupovinaGreedView.SelectedDataKey["LekID"].ToString()),
Convert.ToInt32(kolicinaTextBox.Text),
Convert.ToInt64(kupovinaGreedView.SelectedDataKey["Cena"].ToString()),
Convert.ToInt64(nacinIsporukeDropDownList.SelectedValue),
User.Identity.Name);
}
protected void kupovinaGreedView_SelectedIndexChanged(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(kupovinaGreedView.SelectedDataKey["Lek"].ToString()))
{
LekLabel.Text = kupovinaGreedView.SelectedDataKey["Lek"].ToString();
}
if (!String.IsNullOrEmpty(kupovinaGreedView.SelectedDataKey["Kolicina"].ToString()))
{
kolicinaValidator.MaximumValue = kupovinaGreedView.SelectedDataKey["Kolicina"].ToString();
}
The reason you are getting the error is because of how the database schema is defined. In particular the table Kupac is used in a relation with the table Kupovina such that the KupacID in Kupovina must match a value from the ID field in the Kupac table.
Because of this you cannot insert records into the Kupovina table using a KupacID that doesn't already exist in the Kupac table - it's a Foreign-Key Constraint violation.
You have two options:
make sure you first insert a record into the Kupac table
drop the foreign-key constraint from the database schema if that makes business-sense
But you should examine the output of the KupacAdapter.GetID(korisnickoIme) which is what is supposed to provide the KupacID value. If this method for some reason does not return a valid ID (one from the Kupovina table) than the insert will fail as explained above.
In case you are wondering why the Exception is not begin "handled" than that is because of the code in your catch block. You are basically taking the exception and re-throwing it which is sort of pointless. You might as well just not have a catch block at all..
try {
// ...
}
catch (Exception ex) {
throw ex; // <--- this just re-throws the same exception that was caught!
}
finally {
}
Is, for the most part, nearly equivalent to:
try {
// ...
}
finally {
}
The difference is only in how much of the stack trace is preserved along with the exception, but the exception is bubbled up nonetheless.
The Kupac table does not have the KupacID value in that table that you are trying to insert into the Kupovina table
The KupacID column in the Kupac table is the primary key, while the KupacID column in the Kupovina table is the foreign key pointing back to the primary key
Read up on Foreign Keys
Yes, you are catching the key violation with catch (Exception ex) - however you then rethrow the Exception there.
Do you have an error handler in the calling routine?
throw ex - does not "handle" an error - it is simply raising another (although, the same one in this case - the one that has been caught)
If you are expecting this specific error then you should catch this specific error by catch (SqlException ex) - and then check for this particular error. If it is not this error then throw ex; back up the call-stack... If it is this error then you can ignore (but it would be better to just avoid this kind of INSERT in the first place...)

Unable to handle System.Data.SqlTypes.SqlNullValueException

I have the following code:
public string getFinalCon(string desid) {
string finalConId = null;
try
{
query = "select finalConID from discussions where desid=#did";
com = new SqlCommand(query, con);
com.Parameters.AddWithValue("#did", desid);
con.Open();
sdr = com.ExecuteReader();
while (sdr.Read())
{
if (sdr.GetString(0).Equals("none") == false && sdr.GetString(0)!=null)
{
finalConId = sdr.GetString(0);
break;
}
}
con.Close();
}
catch (Exception)
{
}
return finalConId;
}
As you can see I am catching the "Exception", the global exception. But the problem is that whenever this line finalConId=sdr.GetString(0) is executed, the system throws a System.Data.SqlTypes.SqlNullValueException.
Yes it will surely throw it whenever there is NULL value in the database in the corresponding field. But what I want is that this exception should be caught by the catch block and the function should return the default value of finalConId that is NULL as declared in starting of the function. But this is not happening instead it shows up my error page. I am calling this function like this:
string conid = getFinalCon(Request["id"].ToString());
if (conid == null)
{ /*---some code---*/}
else
{/*---some code---*}
How can I handle this exception?
Don't catch exceptions when you don't need to. The proper way to do this is to test sdr.IsDBNull(0) before calling sdr.GetString(0). If IsDBNull() returns true, then GetString() will throw an exception and you should not call it.
It's also very poor practice to swallow all exceptions without indicating some sort of error. catch { } or catch (Exception) { } should be avoided in almost all cases. If something catastrophic happens (the DB connection goes down, for example) you want to allow that exception to propagate. Otherwise, how is the caller to distinguish between the cases "null value in that column" and "database connection died?"
its better to check value with DBNull.Value or IsDBNull() like this
if (reader.IsDBNull(0) && sdr.GetString(0).Equals("none") == false)
//sdr.GetString(0)!=DBNull.Value)
if you want return null when exception than do like this
string conid;
try
{
conid = getFinalCon(Request["id"].ToString());
}
Catch(Exception ex)
{
conid =null;
}
You get an System.Data.SqlTypes.SqlNullValueException because the program trying to read something is NULL from database. You could fix this by checking if the value is NULL before you read it.
Try the following code:
query = "select finalConID from discussions where desid=#did";
com = new SqlCommand(query, con);
com.Parameters.AddWithValue("#did", desid);
con.Open();
sdr = com.ExecuteReader();
while (sdr.Read())
{
if (sdr.GetString(0).Equals("none") == false && !sdr.IsDBNull(0) )
{
finalConId = sdr.GetString(0);
break;
}
}
con.Close();
enter code here
sdr.IsDBNull(0) will check if the value you want to read is NULL. This gonna fix your error :-)
You can use the following structure inside your 'while' loop (considering if you are using ADO connected based Data Access Layer).
if (reader["X"]is DBNull)
{ a = "N/A";}
else
{ a = reader.GetString(reader.GetOrdinal("X")); }
where X is the name of the alias or row name of your DB and a is the object you are linking that reader to.
one thing to consider, in my example, X is a string.
as you can see, you are handling that specific Null reference.

Categories

Resources