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?
-
as with any exception, look at the innerexception to find out the exact error details. – RPM1984 Oct 19 '10 at 09:55
7 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).
- 994,864
- 265
- 3,241
- 2,902
-
Perfect :) - However.. I just tried to catch the ErrorCode and it says that the error code for a duplicate entry is -2146232060 which seems a bit odd to me? – ebb Oct 19 '10 at 10:15
-
15For future reference: [2601](http://msdn.microsoft.com/en-us/library/aa258747(v=sql.80).aspx) is a unique constraint violation (for SQL Server). – kamranicus Dec 24 '12 at 01:31
-
3What if both SQLServer and Oracle are used? 2601 is only for SQLServer – Learner Jun 10 '13 at 12:12
-
132601 is "unique index" and 2627 is unique constraint [link](http://stackoverflow.com/a/6483854/823247) – walterhuang Jul 02 '14 at 02:57
-
Casting to `SqlException` and checking for vendor specific error codes defeats the purpose of using EF, though... – Telmo Marques May 27 '22 at 15:12
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,508
- 3
- 25
- 30
- 418
- 6
- 14
-
Thanks, this was helpful! I'm going to use this, but with an extra null check on line 7; as written, the catch block will throw a NullReferenceException if ex.InnerException (i.e. the first nested InnerException) is null. – Jon Schneider Jun 21 '16 at 18:21
-
1This worked for me if I only went one layer in, ie ex.InnerException instead of ex.InnerException.InnerException. – ben Feb 18 '19 at 09:25
Now in C# 6.0 you should be able to do something like this:
catch (UpdateException ex) when ((ex.InnerException as SqlException)?.Number == ??????)
{
// Handle exception here
}
- 1,387
- 2
- 17
- 39
Now in C# 7 you can use the is operator
// 2627 is unique constraint (includes primary key), 2601 is unique index
catch (UpdateException ex) when (ex.InnerException is SqlException sqlException && (sqlException.Number == 2627 || sqlException.Number == 2601))
{
}
- 581
- 4
- 21
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();
}
}
- 29,755
- 12
- 86
- 122
-
4I know it's not generally right to respond to a coding question pointing at a library, but in this case I strongly think this is the correct solution. It makes sense for there to be an abstraction at the EF level for this scenario rather than leaking the database provider abstraction into client code. After all, we don't have SQL Server specific code to set up the primary key / unique constraint in the first place, so why should detection of violations be SQL Server specific? I'd rather it was baked into EF, but I'm happy to take this dependency. – Josh Gallagher Dec 15 '21 at 13:57
-
1Definitely the correct approach. I write code that works against either SQL Server or PostGres databases, and the accepted answer requires me to know the error number for both (and which one it is!). This really should be part of EF Core – Auspex Jan 06 '22 at 17:08
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);
}
- 27
- 1
- 9
-
2Notice that your code is inherently exposed to racing conditions in the sense that someone might race-insert an entry between GetSingleByUniqueConstraint and duplicateEntityCheck.Overwrite(). For this reason you should add provisions to perform the update on a catch block around base.Add(); – XDS May 09 '19 at 11:14
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);
}
}
- 21,600
- 19
- 71
- 93