How to capture EF exception - c#

I want to capture the error from entity framework.
So i have the below code
return await _context.SaveChangesAsync(cancellationToken);
if something goes wrong in the above code i want it to be captured as a warning in database, but as of now it is automatically saving in the database as error which has to be warning actually.
below is the error that i get
An exception occurred in the database while saving changes for context
type 'ValuationsReporting.Models.ValuationsReportingContext'.
System.InvalidOperationException: The property 'TemplateTypeId' on
entity type 'ValuationFundTemplate' has a temporary value. Either set
a permanent value explicitly or ensure that the database is configured
to generate values for this property. at
Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.Validate(ModificationCommand
modificationCommand) at
Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.BatchCommands(IReadOnlyList1
entries)+MoveNext() at
Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(DbContext
_, ValueTuple2 parameters, CancellationToken cancellationToken) at
Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func4
operation, Func4 verifySucceeded, TState state, CancellationToken
cancellationToken) at
Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func4
operation, Func4 verifySucceeded, TState state, CancellationToken
cancellationToken) at
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IReadOnlyList`1
entriesToSave, CancellationToken cancellationToken) at
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(Boolean
acceptAllChangesOnSuccess, CancellationToken cancellationToken) at
Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean
acceptAllChangesOnSuccess, CancellationToken cancellationToken)
System.InvalidOperationException
Microsoft.EntityFrameworkCore.Update
I tried using try catch but as soon as it goes to that line the error is logged in database.
Actual code :
try
{
foreach (var template in snapshotDto.SnapshopFundTemplateDtos)
{
if (template.FundId != null)
{
foreach (var fundId in template.FundId)
{
var tempTemplate = allFundTemplates.Where(x => x.ValuationId == valuation.Id && x.FundId == fundId && x.TemplateTypeId == template.TemplateTypeId).FirstOrDefault();
//var tempTemplate = await _valuationFundTemplateRepository.GetOne(valuation.Id, fundId, template.TemplateTypeId, true, cancellationToken);
if (tempTemplate == null)
{
tempTemplate = new ValuationFundTemplate();
tempTemplate.CreatedBy = _userRepository.claimsPrincipal.Identity.Name;
tempTemplate.CreatedOn = DateTime.Now.ToUniversalTime();
isTemplateUpdate = false;
}
//tempTemplate.IsDeleted = false;
//if (template.IsDeleted)
//{
// _valuationFundTemplateRepository.Delete(tempTemplate);
//}
//else
//{
//tempTemplate.IsDeleted = template.IsDeleted;
tempTemplate.IsDeleted = false;
tempTemplate.IsDefaultFundTemplate = template.IsDefault;
tempTemplate.FundId = fundId;
tempTemplate.ValuationId = valuation.Id;
tempTemplate.TemplateTypeId = 0;
tempTemplate.TemplateId = template.TemplateId;
tempTemplate.ModifiedBy = _userRepository.claimsPrincipal.Identity.Name;
tempTemplate.ModifiedOn = DateTime.Now.ToUniversalTime();
tempTemplates.Add(tempTemplate);
if (isTemplateUpdate)
{
_valuationFundTemplateRepository.Update(tempTemplate);
}
else
{
await _valuationFundTemplateRepository.Insert(tempTemplate, cancellationToken);
}
// }
await _valuationFundTemplateRepository.SaveAsync(cancellationToken);//here is where the error occurs which i dont want to capture in database.
if (!isTemplateUpdate)
valuation.ValuationFundTemplate.Add(tempTemplate);
}
}
}
}catch(Exception e)
{
var z = e.Message;
}
public virtual async Task<int> SaveAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return await _context.SaveChangesAsync(cancellationToken);
}

Agreed with the #PrashantPimpale you can simply use try catch.
But for the advanced approach, I would suggest you can use a Global Error Handling Middleware.
Through which you can capture any error occurred in the whole dotnet API/Application.
Just and Elaboration below:
// Extension method used to add the middleware to the HTTP request pipeline.
public static class ErrorHandlingMiddlewareExtensions
{
public static IApplicationBuilder UseErrorHandlingMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ErrorHandlingMiddleware>();
}
}
Then add app.UseErrorHandlingMiddleware(); in the Startup's Configure() method.
You have to first create a ErrorHandlingMiddleware class.
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception ex)
{
await HandleExceptionAsync(httpContext, ex);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
// Implement how you want to handle the error.
}
}

Following on from my comment on the other answer - I have found a solution to this.
There are two things not immediately clear here, so to clarify:
There are legitimate reasons for wanting to suppress some DB errors and treat them as warnings (for instance) e.g. custom triggers checking data integrity
This is NOTHING to do with capturing an exception coming from EF Core, EF Core is DIRECTLY emitting error log lines for DbUpdateExceptions AS WELL AS throwing that exception to be caught by the application
So the solution here is to configure EF Core to change its behaviour and log failures to save changes as warnings instead of errors (could suppress completely - but I'm playing safe). I haven't found actual documentation on this, but with some experimentation have found configuring your context like this works correctly:
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
base.OnConfiguring(builder);
builder
.ConfigureWarnings(x => x
.Log(
(CoreEventId.SaveChangesFailed, LogLevel.Warning)
)
);
}

Related

The instance of entity type 'x' cannot be tracked because another instance with the key value '{Id: 6}' is already being tracked

I am working a asp .netcore 6.0 clean architecture project.
When I try to update a site, I got this error,
System.InvalidOperationException: The instance of entity type 'SiteCode' cannot be tracked because another instance with the key value '{Id: 6}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Before We use services, in there this same code worked fine. Now we move to clean architecture(CQRS and Mediatr). I used same update code but I got this error.
.AsNoTracking()
I tried with
var result = await _DbContext.SiteCodes.FindAsync(request.Id).AsNoTracking(); this line,
But got error as,
'ValueTask<SiteCode?>' does not contain a definition for 'AsNoTracking' and no accessible extension method 'AsNoTracking' accepting a first argument of type 'ValueTask<SiteCode?>' could be found (are you missing a using directive or an assembly reference?) [Application]
Here is my codes
UpdateSiteCommandHandler.cs
public async Task<SiteCode> Handle(UpdateSiteCommand request, CancellationToken cancellationToken)
{
var result = _mapper.Map<SiteCode>(request);
_DbContext.SiteCodes.Update(result); // goes to exception after this line
await _DbContext.SaveChangesAsync(cancellationToken);
return result;
}
GetSiteByIdQueryHandler.cs
public async Task<SiteCode> Handle(GetSiteByIdQuery request, CancellationToken cancellationToken)
{
var result = await _DbContext.SiteCodes.FindAsync(request.Id);
if (result == null)
{
throw new NotFoundException(nameof(SiteCode), request.Id);
}
return result;
}
controller
public async Task<IActionResult> Update(int id, [FromBody] UpdateSiteCommand command)
{
command.Id = id;
var siteCode = await _mediator.Send(new GetSiteByIdQuery(Id: id));
var result = await _mediator.Send(command);
return Ok(result);
}
DependencyInjection.cs
public static class DependencyInjection
{
public static IServiceCollection AddApplication(this IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>)); // I tried with AddScoped() But not work
return services;
}
}
in DbContext
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var entries = ChangeTracker
.Entries()
.Where(e => e.Entity is AuditableEntity && (
e.State == EntityState.Added
|| e.State == EntityState.Modified));
foreach (var entityEntry in entries)
{
if (entityEntry.State == EntityState.Added)
{
((AuditableEntity)entityEntry.Entity).CreatedAt = DateTime.UtcNow;
((AuditableEntity)entityEntry.Entity).CreatedBy = _httpContextAccessor?.HttpContext?.User?.Identity?.Name ?? null;
}
else
{
Entry((AuditableEntity)entityEntry.Entity).Property(p => p.CreatedAt).IsModified = false;
Entry((AuditableEntity)entityEntry.Entity).Property(p => p.CreatedBy).IsModified = false;
}
((AuditableEntity)entityEntry.Entity).ModifiedAt = DateTime.UtcNow;
((AuditableEntity)entityEntry.Entity).ModifiedBy = _httpContextAccessor?.HttpContext?.User?.Identity?.Name ?? null;
}
var result = await base.SaveChangesAsync(cancellationToken)
return result;
}
Anyone has idea how can solve this issue?
EF Core uses ChangeTracker to detect changes in loaded entities, and better (not faster) solution is to load entity for update.
public async Task<SiteCode> Handle(UpdateSiteCommand request, CancellationToken cancellationToken)
{
// the following code will load entitiy if it is still not loaded.
var dbRequest = await _DbContext.SiteCodes.FindAsync(request.Id);
if (dbRequest == null)
throw new Exception("Not found");
_mapper.Map<SiteCode>(request, dbRequest);
await _DbContext.SaveChangesAsync(cancellationToken);
return result;
}
Try to add the method AsNoTracking() after SiteCodes to your query in Handler (GetSiteByIdQueryHandler.cs).
public async Task<SiteCode> Handle(GetSiteByIdQuery request, CancellationToken cancellationToken)
{
var result = await _DbContext.SiteCodes.AsNoTracking().FindAsync(request.Id);
if (result == null)
{
throw new NotFoundException(nameof(SiteCode), request.Id);
}
return result;
}

Azure Function Middleware: How to return a custom HTTP response?

I am exploring Azure Function running on .net 5 and I found out about the new middleware capabilities.
I have built a dummy middleware like this one:
public sealed class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
private readonly ILogger<ExceptionLoggingMiddleware> m_logger;
public ExceptionLoggingMiddleware(ILogger<ExceptionLoggingMiddleware> logger)
{
m_logger = logger;
}
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception unhandledException)
{
m_logger.LogCritical(unhandledException, "Unhandled exception caught: {UnhandledException}", unhandledException.Message);
}
}
}
In my use case, the Azure Function is an HTTP triggered function:
public sealed class StorageAccountsFunction
{
private readonly ILogger<StorageAccountsFunction> m_logger;
public StorageAccountsFunction
(
ILogger<StorageAccountsFunction> logger
)
{
m_logger = logger;
}
[Function("v1-post-storage-account")]
public async Task<HttpResponseData> CreateAsync
(
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "v1/storage-accounts")]
HttpRequestData httpRequestData,
FunctionContext context
)
{
m_logger.LogInformation("Processing a request to create a new storage account");
throw new Exception("Oh no! Oh well..");
}
}
In my Function App running in-process on .net core 3.1, each Function had the responsibility of catching the unhandled exception (via a base class) and returned the appropriate HTTP status code.
I would like to have that logic sit in a middleware instead to have it centralized and avoid any future mistakes.
Question
The exception is caught by the middleware properly. However, I do not see how I can alter the response and return something more appropriate, instead of a 500 Internal Server Error that I get right now?
According to this issue, there is currently no official implementation regarding this, but they also mention a "hacky workaround" until the proper functionality is implemented directly into Azure functions
We created an extension method for FunctionContext:
internal static class FunctionUtilities
{
internal static HttpRequestData GetHttpRequestData(this FunctionContext context)
{
var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
var functionBindingsFeature = keyValuePair.Value;
var type = functionBindingsFeature.GetType();
var inputData = type.GetProperties().Single(p => p.Name == "InputData").GetValue(functionBindingsFeature) as IReadOnlyDictionary<string, object>;
return inputData?.Values.SingleOrDefault(o => o is HttpRequestData) as HttpRequestData;
}
internal static void InvokeResult(this FunctionContext context, HttpResponseData response)
{
var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
var functionBindingsFeature = keyValuePair.Value;
var type = functionBindingsFeature.GetType();
var result = type.GetProperties().Single(p => p.Name == "InvocationResult");
result.SetValue(functionBindingsFeature, response);
}
}
The usage in the middleware looks like this:
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
if (ex.InnerException is *NameOfExceptionYouNeed* e)
{
var req = context.GetHttpRequestData();
var res = await req.ErrorResponseAsync(e.Message);
context.InvokeResult(res);
return;
}
throw;
}
}
This is natively supported now as of version 1.8.0 of Microsoft.Azure.Functions.Worker.
The FunctionContextHttpRequestExtensions class was introduced so now you can just
using Microsoft.Azure.Functions.Worker;
public class MyMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// To access the RequestData
var req = await context.GetHttpRequestDataAsync();
// To set the ResponseData
var res = req!.CreateResponse();
await res.WriteStringAsync("Please login first", HttpStatusCode.Unauthorized);
context.GetInvocationResult().Value = res;
}
}
This code works for me. It is based on the example here: https://github.com/Azure/azure-functions-dotnet-worker/blob/main/samples/CustomMiddleware/ExceptionHandlingMiddleware.cs
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// Simple example which always fails. Use the following in an error condition
var httpReqData = await context.GetHttpRequestDataAsync();
if (httpReqData != null)
{
var newHttpResponse = httpReqData.CreateResponse(HttpStatusCode.InternalServerError);
await newHttpResponse.WriteAsJsonAsync(new { ResponseStatus = "Invocation failed!" }, newHttpResponse.StatusCode);
context.GetInvocationResult().Value = newHttpResponse;
}
}

Show exception created in EF Core repository instead of 500 Error

My goal is to verify if an object's name already exists in my EF Core db, if so: throw a specific error. However, I receive a 500 internal server error.
First I created an index on name in DbContext, including IsUnique and some code to catch the exception in the repository.
Can I maybe add something in the controller that says if errorcode == 2601 then throw "the required exception"? Or is there another way to overcome this 500 error? Thanks in advance for the help!
DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasIndex(c => c.Name)
.IsUnique();
}
Repository:
public async Task<bool> SaveAsync()
{
try
{
return (await _context.SaveChangesAsync() >= 0);
}
catch (DbUpdateException dbEx)
{
SqlException sqlException = dbEx.InnerException as SqlException;
if (sqlException.Number == 2601)
{
throw new Exception("Name already exists. Please provide a different name.");
}
throw new Exception(dbEx.Message);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
Controller:
Public async Task<IActionResult> AddCar([FromBody] Car car)
...
if (!await _repository.SaveAsync())
{
throw new Exception("Fail on save...");
}
...
If you are using ASP.Net Core, you can create you own exception handling middleware.
The error handling middleware class itself may look something like:
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate m_next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
m_next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await m_next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;
string message = "Something is wrong!";
if (exception is MyException)
{
httpStatusCode = HttpStatusCode.NotFound; // Or whatever status code you want to return
message = exception.Message; // Or whatever message you want to return
}
string result = JsonConvert.SerializeObject(new
{
error = message
});
context.Response.StatusCode = (int)httpStatusCode;
context.Response.ContentType = "application/json";
return context.Response.WriteAsync(result);
}
}
You register in Startup.Configure() as:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
Change stdoutLogEnabled="false" to true and then check the logs at stdoutLogFile=".\logs\stdout". The error(s) there might tell you something.
Check that you set up right Environment Name using ASPNETCORE_ENVIRONMENT environment variable as so use correct settings like connection string. On your machine by default you have "Development" environment.
You may use Error Handling middlewares for showing exceptions like
app.UseDeveloperExceptionPage();

WebAPI ActionFilter Await ExecuteActionFilterAsync No Result

I try to create a filter to modify the content. For some reason the
var result = await actionContext.Request.Content.ReadAsStringAsync(); does not wait and returns me empty values. I'm sure that there is data. Checked directly inside controller and header. Is there maybe a workaround. Can be blocking, too (HttpContent seems just to have async methods).
public class AsyncAttribute : FilterAttribute, IActionFilter
{
public async Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
await InternalActionExecuting(actionContext, cancellationToken);
if (actionContext.Response != null)
{
return actionContext.Response;
}
HttpActionExecutedContext executedContext;
try
{
var response = await continuation();
executedContext = new HttpActionExecutedContext(actionContext, null)
{
Response = response
};
}
catch (Exception exception)
{
executedContext = new HttpActionExecutedContext(actionContext, exception);
}
await InternalActionExecuted(executedContext, cancellationToken);
return executedContext.Response;
}
public virtual async Task InternalActionExecuting(HttpActionContext actionContext, CancellationToken cancellationToken)
{
//////////////////////////////////////////////////////////////////////////
var result = await actionContext.Request.Content.ReadAsStringAsync();// <------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
}
public virtual async Task InternalActionExecuted(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
}
}
You should not be accessing the body in an action filter, if you have any thing else accessing the body in parameter binding it might be already gone.
Why are you calling actionExecuted inside the action executing overload? The pattern is one gets called before the action is run and one after.
For some reason it also looks like you are trying to implement the filter pipeline itself inside this method.
If you want to bind data from the body your recommended alternative is to use a formatter. Implement a MediaTypeFormatter and read what you need into a string. See one example
My wild bet here is that something else already read the body stream (and technically you are done at the point) and you can try and rewind it first (it will only work if you are in buffered mode).

WebApi v2 ExceptionHandler not called

How comes that a custom ExceptionHandler is never called and instead a standard response (not the one I want) is returned?
Registered like this
config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
and implemented like this
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new ExceptionResponse
{
statusCode = context.Exception is SecurityException ? HttpStatusCode.Unauthorized : HttpStatusCode.InternalServerError,
message = "An internal exception occurred. We'll take care of it.",
request = context.Request
};
}
}
public class ExceptionResponse : IHttpActionResult
{
public HttpStatusCode statusCode { get; set; }
public string message { get; set; }
public HttpRequestMessage request { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(statusCode);
response.RequestMessage = request;
response.Content = new StringContent(message);
return Task.FromResult(response);
}
}
and thrown like this (test)
throw new NullReferenceException("testerror");
in a controller or in a repository.
UPDATE
I do not have another ExceptionFilter.
I found a trigger for this behavior:
Given URL
GET http://localhost:XXXXX/template/lock/someId
sending this header, my ExceptionHandler works
Host: localhost:XXXXX
sending this header, it doesn't work and the built-in handler returns the error instead
Host: localhost:XXXXX
Origin: http://localhost:YYYY
This might be an issue with CORS requests (I use the WebAPI CORS package globally with wildcards) or eventually my ELMAH logger. It also happens when hosted on Azure (Websites), though the built-in error handler is different.
Any idea how to fix this?
Turns out the default only handles outermost exceptions, not exceptions in repository classes. So below has to be overridden as well:
public virtual bool ShouldHandle(ExceptionHandlerContext context)
{
return context.ExceptionContext.IsOutermostCatchBlock;
}
UPDATE 1
WebAPI v2 does not use IsOutermostCatchBlock anymore. Anyway nothing changes in my implementation, since the new code in ShouldHandle still prevents my Error Handler. So I'm using this and my Error Handler gets called once. I catch errors in Controllers and Repositories this way.
public virtual bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
UPDATE 2
Since this question got so much attention, please be aware that the current solution is the one linked by #JustAMartin in the comments below.
The real culprit here is CorsMessageHandler inserted by EnableCors method in message processing pipline. The catch block intercept any exception and convert into a response before it can reach the HTTPServer try-catch block and ExceptionHandler logic can be invoked
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
CorsRequestContext corsRequestContext = request.GetCorsRequestContext();
HttpResponseMessage result;
if (corsRequestContext != null)
{
try
{
if (corsRequestContext.IsPreflight)
{
result = await this.HandleCorsPreflightRequestAsync(request, corsRequestContext, cancellationToken);
return result;
}
result = await this.HandleCorsRequestAsync(request, corsRequestContext, cancellationToken);
return result;
}
catch (Exception exception)
{
result = CorsMessageHandler.HandleException(request, exception);
return result;
}
}
result = await this.<>n__FabricatedMethod3(request, cancellationToken);
return result;
}

Categories

Resources