I'm trying to catch the exception thrown when I insert a already existing user with the given username into my database. As the title says then I'm using EF. The only exception that's thrown when I try to insert the user into to db is a "UpdateException" - How can I extract this exception to identify whether its a duplicate exception or something else?
10 Answers
catch (UpdateException ex) { SqlException innerException = ex.InnerException as SqlException; if (innerException != null && innerException.Number == ??????) { // handle exception here.. } else { throw; } } Put the correct number at ?????? that corresponds to unique constraint violation (I don't know it from the top of my head).
6 Comments
SqlException and checking for vendor specific error codes defeats the purpose of using EF, though...Because I'm using EntityFramework with C#, I had to make a minor change to this - hope it helps anyone...
try { await db.SaveChangesAsync(); } catch (DbUpdateException ex) { SqlException innerException = ex.InnerException.InnerException as SqlException; if (innerException != null && (innerException.Number == 2627 || innerException.Number == 2601)) { //your handling stuff } else { throw; } } My issue came about because I needed DbUpdateException instead of UpdateException, and my InnerException object had an additional InnerException object that contained the Number I needed...
2 Comments
If you need to do the same in Entity Framework Core you can use the library that I built which provides strongly typed exceptions including UniqueConstraintException: EntityFramework.Exceptions
It allows you to catch types exceptions like this:
using (var demoContext = new DemoContext()) { demoContext.Products.Add(new Product { Name = "a", Price = 1 }); demoContext.Products.Add(new Product { Name = "a", Price = 10 }); try { demoContext.SaveChanges(); } catch (UniqueConstraintException e) { //Handle exception here } } All you have to do is install it from Nuget and call it in your OnConfiguring method:
class DemoContext : DbContext { public DbSet<Product> Products { get; set; } public DbSet<ProductSale> ProductSale { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseExceptionProcessor(); } } 2 Comments
Following @peteski and @Sam
or keyword available since C# 9:
catch (UpdateException ex) when (ex.InnerException is SqlException { Number: 2627 or 2601 }) { } 4 Comments
I think its better if you prevent the exception from happening. If its possible with your code, I would do the following:
When using entity framework, the best thing to do is to first try and get the entry that will cause you the trouble, with LINQ's SingleOrDefault. Now you can update the gotten entity with the instance you wanted to insert, safes you an ID number with auto-increment if you use it. If SingleOrDefault is null you can safely add your entity.
example of code:
public override void AddOrUpdate(CustomCaseSearchCriteria entity) { var duplicateEntityCheck = GetSingleByUniqueConstraint(entity.UserCode, entity.FilterName); if (duplicateEntityCheck != null) { duplicateEntityCheck.Overwrite(entity); base.Update(duplicateEntityCheck); } else base.Add(entity); } public virtual CustomCaseSearchCriteria GetSingleByUniqueConstraint(string userCode, string filterName) { return GetAllInternal().SingleOrDefault(sc => sc.UserCode == userCode && sc.FilterName == filterName); } 1 Comment
For any DB type you can use reflection and the correspondent error number (for MySql 1062):
try { var stateEntries = base.SaveChanges(); } catch (Exception e) { if(e is DbUpdateException) { var number = (int)e.InnerException.GetType().GetProperty("Number").GetValue(e.InnerException); if (e.InnerException!= null && (number == 1062)) { //your handling stuff } else { messages.Add(e.InnerException.Source, e.InnerException.Message); } } else if (e is NotSupportedException || e is ObjectDisposedException || e is InvalidOperationException) { messages.Add(e.InnerException.Source, e.InnerException.Message); } } Comments
In the EF core, the UpdateException became DbUpdateException. It is not guaranteed the inner exception will be SQLException, so, you would need to cycle through inner exceptions to find SqlException, something like:
try { return await context.SaveChangesAsync(); } catch (DbUpdateException ex) { var inner = ex.InnerException; while(inner != null) { if (inner is SqlException sqlException) { if (sqlException.Number is 2627 or 2601) throw/return/log here break; } inner = inner.InnerException; } // the error was not due a duplicate } Comments
You may consider using EntityFramework.Exceptions package EntityFramework.Exceptions by Giorgi Dalakishvili. It simplifies DbUpdateException handling by handling all the database specific details and throwing different exceptions. From my point of view, this is how EntityFramework should handle exceptions