Log all exception information for any kind of exception - c#

I'm developing with C#, ASP.NET MVC Web Api, Entity Framework and .NET Framework 4.0.
I have this code to log an exception:
public void LogCompleteException(
string controllerName,
string actionName,
Exception exception)
{
string exceptionMessage = string.Empty;
Exception e = exception;
if (e.InnerException == null)
e = null;
else
while (e.InnerException != null) e = e.InnerException;
if (e == null)
exceptionMessage = exception.Message;
else
exceptionMessage = string.Format("{0}\n\rInnerException: {1}", exception.Message, e.Message);
_logger.ErrorFormat(
LogTextFormatString,
ExceptionText,
controllerName,
actionName,
exceptionMessage);
}
But on my log file I have found this:
Validation failed for one or more entities. See
'EntityValidationErrors' property for more details.
When I wrote 'See 'EntityValidationErrors' property for more details.', I'm only showing an example where I haven't log an important property.
There are a lot of kind of exceptions; but with my method I'm not logging all the relevant information because there could be properties like 'EntityValidationErrors' that I don't log.
When I pass the exception to log it I don't know what properties it has, and I don't know how to log each property it has.
Do you know a method to log an exception completely? My code doesn't long exceptions with an EntityValidationErrors property or any other important property.
I'm doing the logging with log4net.

Since the inner exception is an exception itself, perhaps you can just recurse and reuse the method you already have:
if (e.InnerException != null)
{
LogCompleteException(controllerName, actionName, e.InnerException);
}
If this does work, however, you will still be missing the EntityValidationErrors.
Another method, which I used recently is to just explicitly trap and log the exception where it occurs:
try
{
db.Add(something);
db.SaveChanges();
}
catch (DbEntityValidationException ex)
{
StringBuilder sb = new StringBuilder();
// collect some extra info about the exception before logging
foreach (var eve in ex.EntityValidationErrors)
{
sb.AppendLine(String.Format("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:", eve.Entry.Entity.GetType().Name, eve.Entry.State));
foreach (var ve in eve.ValidationErrors)
{
sb.AppendLine(String.Format("Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage));
}
}
logger.Error("There was an error while trying to parse and save something!\n" + sb.ToString(), ex);
}
If you want the Exception.Data dictionary entries, you can add those as well:
foreach (DictionaryEntry entry in ex.Data)
{
// you will want to use a StringBuilder instead of concatenating strings if you do this
exceptionMessage = exceptionMessage + string.Format("Exception.Data[{0}]: {1}", entry.Key, entry.Value);
}
As for properties for Custom exception classes just as the EntityValidationErrors, I would just trap and parse those where they occur. Otherwise you would have to override ToString() on every exception type or use some reflection hackery to enumerate all the properties which would significantly clutter the logs with properties you dont care about.

you could use elmah and the log4net appender. Elmah logs catches all exceptions and can log them to a log4net instance.

Related

EF6 Throws Exception for EntityValidationErrors When I Try to Update Entity

I have a problem when I update entity in EF6. The code looks like this:
public PICCOSSourceCost GetCOSSourceCost(int sourceCostID)
{
return ERPContext.PICCOSSourceCost.Where(sc => sc.ID == sourceCostID && !sc.Deleted).FirstOrDefault();
}
public PICCOSSourceCost UpdateCOSSourceCost(PICCOSSourceCost sourceCost, bool saveChanges = true)
{
var sc = GetCOSSourceCost(sourceCost.ID);
if (sc == null)
{
throw new PICObjectNotFoundException<PICCOSSourceCost>(sourceCost, new List<string>()
{
nameof(PICCOSSourceCost.PICCOSSourceID),
nameof(PICCOSSourceCost.PICCOSPriceTypeID),
nameof(PICCOSSourceCost.Price),
nameof(PICCOSSourceCost.EffectiveDate)
});
}
sc.PICCOSSourceID = sourceCost.PICCOSSourceID;
sc.PICCOSPriceTypeID = sourceCost.PICCOSPriceTypeID;
sc.Price = sourceCost.Price;
sc.EffectiveDate = sourceCost.EffectiveDate;
sc.Deleted = sourceCost.Deleted;
sc.CreatedBy = sourceCost.CreatedBy;
sc.CreatedDate = sourceCost.CreatedDate;
sc.LastModifiedBy = sourceCost.LastModifiedBy;
sc.LastModifiedDate = sourceCost.LastModifiedDate;
if (saveChanges) ERPContext.SaveChanges();
return sc;
}
As you can see that the "GetCOSSourceCost" method get entity from EF. And the first parameter "sourceCost" in the "UpdateCOSSourceCost" method is passed in from FrontEnd, which is also got from EF6.
When I debug the code, there is an "System.Data.Entity.Validation.DbEntityValidationException" occurred. I don't know why. I think that it should be okay because I just get an entity object and just change its properties and save changes. Is it because there are two references to the same object?
If I remove the property assignment code, the error will disappear.
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
Do you know why it throws this exception? Please help me. Thank you so much!
Based on this
http://mattrandle.me/viewing-entityvalidationerrors-in-visual-studio/
You can use is a special debugger variable- $exception
To view the EntityValidationErrors collection you can use below to show watch windows
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
Thank you for your help so much. I have solved my issue by the following link:
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details
For the sake of this link, I debugged and find the root cause of the entity validation error. Some required fields are set to NULL value.
The code from the answer is very useful:
try
{
// Your code...
// Could also be before try if you know the exception occurs in SaveChanges
context.SaveChanges();
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:", eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}

Elmah modify current exception before logging

I have an Asp.net MVC project which is using Elmah to log the exceptions.
The project is using Entity Framework which can throw a DbEntityValidationException exception where is specifies which properties couldn't be saved into Database.
I am trying to capture that exception (as is not logged in details by Elmah), and append the info to the current exception.
The problem is that, if i modify the current exception, the changes are not captured by the Elmah. Instead, i have to raise another exception with the details about DbEntityValidationException, but this will create two consecutive errors logs.
Is possible to append some extra text to the exception before elmah logs it?
public class ErrorHandleAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled == true)
return;
Exception exception = filterContext.Exception;
Exception dbEntityException = exception;
while (dbEntityException != null && dbEntityException is System.Data.Entity.Validation.DbEntityValidationException == false)
{
dbEntityException = dbEntityException.InnerException;
}
if (dbEntityException != null && dbEntityException is System.Data.Entity.Validation.DbEntityValidationException)
{
DbEntityValidationException dbEntityValidationException = dbEntityException as DbEntityValidationException;
StringBuilder sb = new StringBuilder();
sb.AppendLine("Db Entity Validation Exception");
foreach (var item in dbEntityValidationException.EntityValidationErrors)
{
sb.AppendFormat("Entity: {0} {1}", item.Entry.Entity.ToString(), Environment.NewLine);
foreach (var error in item.ValidationErrors)
{
sb.AppendFormat("{0}: {1} {2}", error.PropertyName, error.ErrorMessage, Environment.NewLine);
}
}
exception = new Exception(sb.ToString(), exception);
ErrorSignal.FromCurrentContext().Raise(exception);
}
}
}

Catch DB error details

I want a better way to catch database error details.
I'm currently using :
try
{
dbconn.table.AddObject(newRow);
dbconn.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine("DB fail ID:" + Row.id);
}
many times I found the Exception ex can no give me details on how the exception happen.
I think these exception most likely to be the DB connection kind.
So is there a better way to catch this ?
You should also output the exception. Most of the time, it holds useful and detailed information (e.g. names of violated constraints). Try this:
try
{
dbconn.table.AddObject(newRow);
dbconn.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine("DB fail ID:" + Row.id);
Console.WriteLine(ex.ToString());
}
For full details, use the ToString() method, it will give you the stack trace as well, not only the error message.
Use Console.WriteLine(ex.GetType().FullName) (or put a breakpoint and run under a debugger) to see the actual exception type being thrown. Then visit MSDN to see its description and base classes. You need to decide which of the base classes provides you with the information needed by exposing such properties. Then use that class in your catch() expression.
For Entity Framework, you might end up with using EntityException and then checking the InnerException property for the SQL exception object that it wraps.
try
{
dbconn.table.AddObject(newRow);
dbconn.SaveChanges();
}
catch (EntityException ex)
{
Console.WriteLine("DB fail ID:" + Row.id + "; Error: " + ex.Message);
var sqlExc = ex.InnerException as SqlException;
if (sqlExc != null)
Console.WriteLine("SQL error code: " + sqlExc.Number);
}
Instead of Exception use SqlException.
SqlException give you more detail. it has a Number property that indicate type of error and you can use that Number in a switch case to give some related information to user.
In short, yes there is a better way to handle it. The 'how' of it is up to you.
Exception handling in C# goes from the most specific exception type to the least specific. Also, you aren't limited to using just one catch block. You can have many of them.
As an example:
try
{
// Perform some actions here.
}
catch (Exception exc) // This is the most generic exception type.
{
// Handle your exception here.
}
The above code is what you already have. To show an example of what you may want:
try
{
// Perform some actions here.
}
catch (SqlException sqlExc) // This is a more specific exception type.
{
// Handle your exception here.
}
catch (Exception exc) // This is the most generic exception type.
{
// Handle your exception here.
}
In Visual Studio, it is possible to see a list of (most) exceptions by pressing CTRL+ALT+E.

DbEntityValidationException - How can I easily tell what caused the error?

I have a project that uses Entity Framework. While calling SaveChanges on my DbContext, I get the following exception:
System.Data.Entity.Validation.DbEntityValidationException: Validation
failed for one or more entities. See 'EntityValidationErrors' property
for more details.
This is all fine and dandy, but I don't want to attach a debugger every time this exception occurs. More over, in production environments I cannot easily attach a debugger so I have to go to great lengths to reproduce these errors.
How can I see the details hidden within the DbEntityValidationException?
The easiest solution is to override SaveChanges on your entities class. You can catch the DbEntityValidationException, unwrap the actual errors and create a new DbEntityValidationException with the improved message.
Create a partial class next to your SomethingSomething.Context.cs file.
Use the code at the bottom of this post.
That's it. Your implementation will automatically use the overriden SaveChanges without any refactor work.
Your exception message will now look like this:
System.Data.Entity.Validation.DbEntityValidationException: Validation
failed for one or more entities. See 'EntityValidationErrors' property
for more details. The validation errors are: The field PhoneNumber
must be a string or array type with a maximum length of '12'; The
LastName field is required.
You can drop the overridden SaveChanges in any class that inherits from DbContext:
public partial class SomethingSomethingEntities
{
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
    
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
    
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
    
// Throw a new DbEntityValidationException with the improved exception message.
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
}
The DbEntityValidationException also contains the entities that caused the validation errors. So if you require even more information, you can change the above code to output information about these entities.
See also: http://devillers.nl/improving-dbentityvalidationexception/
As Martin indicated, there is more information in the DbEntityValidationResult. I found it useful to get both my POCO class name and property name in each message, and wanted to avoid having to write custom ErrorMessage attributes on all my [Required] tags just for this.
The following tweak to Martin's code took care of these details for me:
// Retrieve the error messages as a list of strings.
List<string> errorMessages = new List<string>();
foreach (DbEntityValidationResult validationResult in ex.EntityValidationErrors)
{
string entityName = validationResult.Entry.Entity.GetType().Name;
foreach (DbValidationError error in validationResult.ValidationErrors)
{
errorMessages.Add(entityName + "." + error.PropertyName + ": " + error.ErrorMessage);
}
}
To view the EntityValidationErrors collection, add the following Watch expression to the Watch window.
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
I'm using visual studio 2013
While you are in debug mode within the catch {...} block open up the "QuickWatch" window (ctrl+alt+q) and paste in there:
((System.Data.Entity.Validation.DbEntityValidationException)ex).EntityValidationErrors
This will allow you to drill down into the ValidationErrors tree. It's the easiest way I've found to get instant insight into these errors.
For Visual 2012+ users who care only about the first error and might not have a catch block, you can even do:
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors.First().ValidationErrors.First().ErrorMessage
To quickly find a meaningful error message by inspecting the error during debugging:
Add a quick watch for:
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
Drill down into EntityValidationErrors like this:
(collection item e.g. [0]) > ValidationErrors > (collection item e.g. [0]) > ErrorMessage
Actually, this is just the validation issue, EF will validate the entity properties first before making any changes to the database.
So, EF will check whether the property's value is out of range, like when you designed the table. Table_Column_UserName is varchar(20). But, in EF, you entered a value that longer than 20.
Or, in other cases, if the column does not allow to be a Null.
So, in the validation process, you have to set a value to the not null column, no matter whether you are going to make the change on it.
I personally, like the Leniel Macaferi answer. It can show you the detail of the validation issues
I think "The actual validation errors" may contain sensitive information, and this could be the reason why Microsoft chose to put them in another place (properties). The solution marked here is practical, but it should be taken with caution.
I would prefer to create an extension method. More reasons to this:
Keep original stack trace
Follow open/closed principle (ie.: I can use different messages for different kind of logs)
In production environments there could be other places (ie.: other dbcontext) where a DbEntityValidationException could be thrown.
For Azure Functions we use this simple extension to Microsoft.Extensions.Logging.ILogger
public static class LoggerExtensions
{
public static void Error(this ILogger logger, string message, Exception exception)
{
if (exception is DbEntityValidationException dbException)
{
message += "\nValidation Errors: ";
foreach (var error in dbException.EntityValidationErrors.SelectMany(entity => entity.ValidationErrors))
{
message += $"\n * Field name: {error.PropertyName}, Error message: {error.ErrorMessage}";
}
}
logger.LogError(default(EventId), exception, message);
}
}
and example usage:
try
{
do something with request and EF
}
catch (Exception e)
{
log.Error($"Failed to create customer due to an exception: {e.Message}", e);
return await StringResponseUtil.CreateResponse(HttpStatusCode.InternalServerError, e.Message);
}
Use try block in your code like
try
{
// Your code...
// Could also be before try if you know the exception occurs in SaveChanges
context.SaveChanges();
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
You can check the details here as well
http://mattrandle.me/viewing-entityvalidationerrors-in-visual-studio/
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details
http://blogs.infosupport.com/improving-dbentityvalidationexception/

Unable to Catch Specific Exception

I am serializing a XML File.During the serialization ,I am receiving general exception.It is hard trace the problem.
my code is:
try
{
string m_fileName = #"d:\Xml\Person.xml";
XmlSerializer xmlPerSerlzr = new XmlSerializer(typeof(person));
txtWrt = new StreamWriter(m_fileName);
xmlPerSerlzr.Serialize(txtWrt, person);
}
catch(Exception serExp)
{
MessageBox.Show("Exception is :" + serExp.Message.ToString());
}
Error Message :
There was an error reflecting type "Person"
My question is how can i force the CLR to emit the exact error ?
Check the type of the exception, e.g.
serExp.GetType().ToString()
and check for an inner exception (both type and message).
That should give you some more useful info.
Use:
exc.ToString();
In Debug mode in Exception DialogBox, select View details option.
Probably You don't implement 0-parameter constructor.
I think it is better to check also the stack trace and the inner exception.
You can use something like that
string GetExceptionString(Exception ex)
{
string str = "";
while (ex != null)
{
str += ex.Message + "\n" + ex.StackTrace;
ex = ex.InnerException;
}
return str;
}
Use
serExp.StackTrace
instead of
serExp.Message.ToString()
Set a breakpoint in the catch clause, then run in debug mode. You can then explore the exception object easier and figure out what is going on.

Categories

Resources