Why are some exceptions a few levels deep? - c#

I have a simple entity insert as follows:
using (var db = new MyContext())
{
var item = new Artist();
TryUpdateModel(item);
if (ModelState.IsValid)
{
db.Artist.Add(item);
try
{
db.SaveChanges();
gvArtist.DataBind();
gvArtist.Visible = true;
}
catch (Exception e)
{
Master.Warning = e.InnerException.InnerException.Message;
}
}
}
e.Message and e.InnerException.Message both equate to:
"An error occurred while updating the entries. See the inner exception for details."
But, e.InnerException.InnerException.Message gives the exception I'm looking for, which is:
"Violation of UNIQUE KEY constraint 'UQ_artist_Cuid'. Cannot insert duplicate key in object 'dbo.artist'. The duplicate key value is (11). The statement has been terminated."
I'm worried about missing other exceptions, or causing an exception if I just keep
Master.Warning = e.InnerException.InnerException.Message;
in play.

Your fears are completely founded. Something like this is what you're looking for.
catch (Exception ex)
{
while (ex.InnerException != null)
{
ex = ex.InnerException;
}
Master.Message = ex.Message;
}
The reason for the errors being varying levels deep is that the errors can occur in different sections of code, and they may bubble up through a varying number of methods that wrap them inside other exceptions. You can't plan for them to come from a specific level.

Related

How get Detail of InnerException

Regarding the duplicated. I can access the Message property but not the Detail property even when I can see is part of the Exception object during debuging. So the question is Why cant access Detail property.
I catch an exception.
catch (Exception ex)
{
string msg = ex.InnerException.InnerException.Message;
// say Exception doesnt have Detail property
// string detail = ex.InnerException.InnerException.Detail;
return Json(new { status = "Fail", message = msg, detail: detail });
}
ex doesnt say anthing
ex.InnerException show same message
ex.InnerException.InnerException. finally some real message, "db table duplicated key"
ex.InnerException.InnerException.Message I can get the message.
But cant get the Detail "the guilty key" even when there is one property Detail
So how can I get the Detail?.
Bonus: Why have to go deep InnerException twice to get some meaningfull message?
I think the most elegant way to do this now is using C# 6 when keyword in a catch statement and C# 7 is operator.
try
{
//your code
}
catch (DbUpdateException ex) when (ex.InnerException is PostgresException pex)
{
string msg = pex.Message;
string detail = pex.Detail;
return Json(new { status = "Fail", message = msg, detail: detail });
}
The trick is to recognize the type of exception being thrown and cast the General Exception to the correct Type where you will then have access to extended properties for that Exception type.
for example:
if (processingExcption is System.Data.Entity.Validation.DbEntityValidationException)
{
exceptionIsHandled = true;
var entityEx = (System.Data.Entity.Validation.DbEntityValidationException)processingExcption;
foreach (var item in entityEx.EntityValidationErrors)
{
foreach (var err in item.ValidationErrors)
returnVal.Invalidate(SystemMessageCategory.Error, err.ErrorMessage);
}
}
else if (processingExcption is System.Data.SqlClient.SqlException && ((System.Data.SqlClient.SqlException)processingExcption).Number == -2)//-2 = Timeout Exception
{
exceptionIsHandled = true;
returnVal.Invalidate(SystemMessageCategory.Error, "Database failed to respond in the allotted time. Please retry your action or contact your system administrator for assistance.",
messageCode: Architecture.SystemMessage.SystemMessageCode.DBTimeout);
}
The fact that the detail you are looking for is 2 inner exceptions deep is incidental. Depending on how many times the exception is caught and wrapped will determine how deep the exception you care about is - your best bet is to iterate through the exception stack looking for exception types you wish to handle.
Referring to your own answer I commented, you definitely should be much more defensive, otherwise you risk of a null reference exception from within your catch clause.
catch (Exception ex)
{
string Detail = string.Empty;
while ( ex != null )
{
if ( ex is Npgsql.NpgsqlException )
{
// safe check
Npgsql.NpgsqlException ex_npg = (Npgsql.NpgsqlException)ex;
Details = ex_npg.Detail;
}
// loop
ex = ex.InnerException;
}
// warning, Detail could possibly still be empty!
return Json(new { status = "Fail", detail = Detail });
}
You cannot get details more than found in this exception
To show real exception loop over innerexceptions until it is null. Then you reached the first one
The exception was thrown from a source class or function then readed by upper level class that throw it with more global details because there is no error handling on the source
Well, it's very sad, but the inner exception is not a magic stick. Usually it's just an object that author of the code that you call puts as the second parameter of the Exception constructor. So, the general answer: "no way". But debugger sometimes could help :). I would say - call stack of the exception usually more descriptive the InnerException.
A quick solution would be to click on the "Detail" property in the "Quick Watch" window. Your answer will be in "Expression" texbox at the top of the quick watch window. Example, the expression for Postgres duplicate detail is:
((Npgsql.PostgresException)ex.InnerException.InnerException).Detail
Here is my function to get some more info from Postgres exception
catch (Exception ex) {
// Get PGSQL exception info
var msg = ExceptionMessage (ex);
}
public static string ExceptionMessage (Exception ex) {
string msg = ex.Message;
var pgEx = ex as PostgresException;
if (pgEx != null) {
msg = pgEx.Message;
msg += pgEx.Detail != null ? "\n"+pgEx.Detail.ToStr() : "";
msg += pgEx.InternalQuery != null ? "\n"+pgEx.InternalQuery.ToStr() : "";
msg += pgEx.Where != null ? "\n"+ pgEx.Where : "";
}
return msg;
}
Thanks Maciej
this solution is great to intercept PostgreSQL Errors
Only correction I did on this
msg += pgEx.Detail != null ? "\n"+pgEx.Detail.ToStr() : "";
msg += pgEx.InternalQuery != null ? "\n"+pgEx.InternalQuery.ToStr() : "";
instead
msg += pgEx.Detail != null ? "\n" + pgEx.Detail.ToString() : "";
msg += pgEx.InternalQuery != null ? "\n" + pgEx.InternalQuery.ToString() : "";

How to Identify the primary key duplication from a SQL Server 2008 error code?

I want to know how we identify the primary key duplication error from SQL Server error code in C#.
As a example, I have a C# form to enter data into a SQL Server database, when an error occurs while data entry, how can I identify the reason for the error from the exception?
If you catch SqlException then see its number, the number 2627 would mean violation of unique constraint (including primary key).
try
{
// insertion code
}
catch (SqlException ex)
{
if (ex.Number == 2627)
{
//Violation of primary key. Handle Exception
}
else throw;
}
MSSQL_ENG002627
This is a general error that can be raised regardless of whether a
database is replicated. In replicated databases, the error is
typically raised because primary keys have not been managed appropriately across the topology.
This is an old thread but I guess it's worth noting that since C#6 you can:
try
{
await command.ExecuteNonQueryAsync(cancellation);
}
catch (SqlException ex) when (ex.Number == 2627)
{
// Handle unique key violation
}
And with C#7 and a wrapping exception (like Entity Framework Core):
try
{
await _context.SaveChangesAsync(cancellation);
}
catch (DbUpdateException ex)
when ((ex.InnerException as SqlException)?.Number == 2627)
{
// Handle unique key violation
}
The biggest advantage of this approach in comparison with the accepted answer is:
In case the error number is not equal to 2627 and hence, it's not a unique key violation, the exception is not caught.
Without the exception filter (when) you'd better remember re-throwing that exception in case you can't handle it. And ideally not to forget to use ExceptionDispatchInfo so that the original stack is not lost.
In case of Entity Framework, the accepted answer won't work and the error will end up not being caught. Here is a test code, only the entity catch statement will be hit or of course the generic exception if entity statement removed:
try
{
db.InsertProcedureCall(id);
}
catch (SqlException e0)
{
// Won't catch
}
catch (EntityCommandExecutionException e1)
{
// Will catch
var se = e1.InnerException as SqlException;
var code = se.Number;
}
catch (Exception e2)
{
// if the Entity catch is removed, this will work too
var se = e2.InnerException as SqlException;
var code = se.Number;
}
Working code for filter only duplicate primary key voilation exception
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
.........
try{
abc...
}
catch (DbUpdateException ex)
{
if (ex.InnerException.InnerException is SqlException sqlEx && sqlEx.Number == 2601)
{
return ex.ToString();
}
else
{
throw;
}
}
Note fine detial :- ex.InnerException.InnerException not ex.InnerException

How to handle exception from specific database error

I am trying to create a transaction like so:
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required,
options))
{
try
{
dbContext.MyTable.PartnerId = someGuid;
dbContext.SaveChanges();
scope.Complete();
dbContext.AcceptAllChanges()
}
catch (Exception ex)
{
log.LogMessageToFile("Exception - ExceptionType: " +
ex.GetType().ToString() + "Exception Messsage: " + ex.Message);
}
}
I know if I try to insert an item manully in sql with a duplicate in a specific column, I get the following error from sql:
Cannot insert duplicate key row in object 'dbo.MyTable' with unique index 'idx_PartnerId_notnull'. The duplicate key value is (7b072640-ca81-4513-a425-02bb3394dfad).
How can I programatically catch this exception specifically, so I can act upon it.
This is the constraint I put on my column:
CREATE UNIQUE NONCLUSTERED INDEX idx_yourcolumn_notnull
ON YourTable(yourcolumn)
WHERE yourcolumn IS NOT NULL;
Try this:
try {
}
catch (SqlException sqlEx) {
}
catch (Exception ex) {
}
SQL errors and warnings that happen on the server side are caught in this exception.
Read about it here:
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlexception(v=vs.110).aspx
The above answer would allow you to catch the SqlException, but you would need to further refine the handling within the 'SqlException' catch block if you only want to inform the user of a particular error. The SqlException class has a property of 'ErrorCode' from which you can derive the actual error being produced by the server. Try doing something like below:
try
{
}
catch (SqlException sqlEx)
{
if(sqlEx.ErrorCode == 2601)
{
handleDuplicateKeyException();
}
}
2601 is the actual error code produced by SQL Server for you particular error. For a full list just run the SQL:
SELECT * FROM sys.messages
Use SqlException's number property.
For duplicate error the number is 2601.
catch (SqlException e)
{
switch (e.Number)
{
case 2601:
// Do something.
break;
default:
throw;
}
}
List of error codes
SELECT * FROM sysmessages
You can catch it by its type:
try
{
// ...
}
catch (SpecialException ex)
{
}
catch (Exception ex)
{
}
EDIT: According to Ivan G's answer, you will get an SqlException, which has an error ErrorCode property that probably specific. So you have to check the error code for this type of error.
you can check exception text or it's other parameters when it is thrown, so then you can act like you wan conditionally
like :
catch(SqlException ex)
{
if(ex.Message.Contains("Cannot insert duplicate key row in object"))
{
}
}
or exception number like
catch(SqlException ex)
{
switch (ex.Number)
{
case : someNumber:
{
//..do something
break...;
}
}
}

Catching specific exception

How can I catch specific exception using c# ?
In my database there is unique index on some columns.
when user inserts duplicate record this exception has been throw :
Cannot insert duplicate key row in
object 'dbo.BillIdentity' with unique
index 'IX_BillIdentity'. The
statement has been terminated.
How can I catch this exception?
Currently I am checking using this code :
catch (Exception ex) {
if (ex.Message.Contains("Cannot insert duplicate key row in object 'dbo._BillIdentity' with unique index 'IX__BillIdentity")) {
string ScriptKey = "$(function() {ShowMessage('Error');});";
ScriptManager.RegisterStartupScript(Page, GetType(), "script", ScriptKey, true);
}
}
I think its bad smell code.
Is there any better way?
Handle SqlException only in this case.
[Edit]
To check duplicate key exception in MS SQL server:
try
{
// try to insert
}
catch (SqlException exception)
{
if (exception.Number == 2601) // Cannot insert duplicate key row in object error
{
// handle duplicate key error
return;
}
else
throw; // throw exception if this exception is unexpected
}
Edit:
Where 2601 come from?
select *
from sys.messages
where text like 'Cannot insert duplicate key%'
Returns:
message_id language_id severity is_event_logged text
----------- ----------- -------- --------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2601 1033 14 0 Cannot insert duplicate key row in object '%.*ls' with unique index '%.*ls'. The duplicate key value is %ls.
Using exception.Number and referencing sys.messages view you can handle any specific MS SQL exception.
You haven't shown the type of exception which is thrown, but you can catch that specific exception type. For example:
catch (DuplicateKeyException e) {
...
}
It's possible that there won't be a specific exception type for just this error - but if you have to catch something fairly general like SqlException you can then look for more details within the class itself. For example in SqlException there's an Errors property where you can look at more detailed information about each of the (possibly multiple) errors at the database side. Each SqlError then has a Number property which will give the type of error. You can always fall back to the message if you absolutely have to, but you then need to be aware of the possibility of the message changing for different cultures etc.
Note that if you're not really handling the exception, you should probably rethrow it:
catch (SqlException e) {
if (CheckWeCanHandle(e)) {
// Mess with the ScriptManager or whatever
} else {
throw;
}
}
I've just picked up a project where someone went down this route:
Catch ex As SqlException
Select Case ex.Number
Case 2601
...
Note the following (from sys.messages in SQL Server):
2601 - Cannot insert duplicate key row in object '%.*ls' with unique index '%.*ls'.
But what about this..?
2627 - Violation of %ls constraint '%.*ls'. Cannot insert duplicate key in object '%.*ls'."
I just spent some time tracking down exactly this problem.
And what if we change DB provider? Presumably 2601 is not absolutely universal... This stinks, IMO. And if you are (were) dealing with this in your presentation layer, I think there are bigger questions to ask.
If this must be the mechanism of choice, bury it deep, deep down in the DAL and let a custom Exception percolate up. That way, changes to the data store (or, ideally, this mechanism) have a much more limited area of effect and you can handle the case consistently without any questions in the presentation layer.
I'm currently leaning towards doing a light-weight SELECT for an ID on an open connection and avoiding the exception altogether.
As documented here, you can use exception filters. Example:
try
{ /* your code here */ }
catch (SqlException sqlex) when (sqlex.Number == 2627)
{ /* handle the exception */ }
Working code for filter only duplicate primary key violation exception
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
try {
abc...
} catch (DbUpdateException ex) {
if (ex.InnerException.InnerException is SqlException sqlEx && sqlEx.Number == 2601) {
return ex.ToString();
} else {
throw;
}
}
Note fine detail :- ex.InnerException.InnerException not ex.InnerException
You could only catch the SqlException for starters
catch (SqlException ex) {
if (ex.Message.Contains("Cannot insert duplicate key row in object 'dbo._BillIdentity' with unique index 'IX__BillIdentity")) {
string ScriptKey = "$(function() {ShowMessage('Error');});";
ScriptManager.RegisterStartupScript(Page, GetType(), "script", ScriptKey, true);
}
}

Catch SqlException when Attempting NHibernate Transaction

I need to see an errorcode produced by a SqlException - however, I can't get one to fire. I use NHibernate and have a SQL UNIQUE CONSTRAINT setup on my table. When that constraint is violated, I need to catch the error code and produce a user-friendly message based off of that. Here is a sample of my try/catch:
using (var txn = NHibernateSession.Current.BeginTransaction()) {
try {
Session["Report"] = report;
_reportRepository.SaveOrUpdate(report);
txn.Commit();
Fetch(null, report.ReportId, string.Empty);
} catch (SqlException sqlE) {
var test = sqlE.ErrorCode;
ModelState.AddModelError("name", Constants.ErrorMessages.InvalidReportName);
return Fetch(report, report.ReportId, true.ToString());
} catch (InvalidStateException ex) {
txn.Rollback();
ModelState.AddModelErrorsFrom(ex, "report.");
} catch (Exception e) {
txn.Rollback();
ModelState.AddModelError(String.Empty, Constants.ErrorMessages.GeneralSaving);
}
}
Please pardon my ignorance.
Check this out which illustrates how to catch a GenericADOException and look at the InnerException property:
catch (GenericADOException ex)
{
txn.Rollback();
var sql = ex.InnerException as SqlException;
if (sql != null && sql.Number == 2601)
{
// Here's where to handle the unique constraint violation
}
...
}
Instead of catching a SqlException directly in your controller, I'd set up a SQLExceptionConverter to translate it to a more meaningful exception.

Categories