36

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?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
ebb
  • 9,297
  • 18
  • 72
  • 123

8 Answers8

42
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).

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 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
  • 15
    For 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
  • 3
    What if both SQLServer and Oracle are used? 2601 is only for SQLServer – Learner Jun 10 '13 at 12:12
  • 13
    2601 is "unique index" and 2627 is unique constraint [link](http://stackoverflow.com/a/6483854/823247) – walterhuang Jul 02 '14 at 02:57
  • 1
    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
  • I like the solution with EntityFrameworkCore.Exceptions.SqlServer package. Just install the package and configure it with optionsBuilder.UseExceptionProcessor(). Then we get UniqueConstraintException exception. This is much clean solution for me. – Praveen Dec 09 '22 at 10:33
25

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...

Olivier De Meulder
  • 2,493
  • 3
  • 25
  • 30
Musical Coder
  • 449
  • 8
  • 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
  • 2
    This 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
19

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
}
peteski
  • 1,455
  • 3
  • 18
  • 40
13

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))
{

}
Sam
  • 601
  • 6
  • 22
9

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();
    }
}
Giorgi
  • 30,270
  • 13
  • 89
  • 125
  • 5
    I 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
  • 1
    Definitely 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
3

Following @peteski and @Sam

or keyword available since C# 9:

catch (UpdateException ex) when (ex.InnerException is SqlException { Number: 2627 or 2601 })
{

}
Parsa99
  • 307
  • 1
  • 13
  • Isn't this really more of a comment than an answer? cool language feature to point out but it looks like a comment, and if it is not intended to be, do provide a complete solution where you in fact perhaps explain why these exception numbers are there and what they give, will be even more useful to the index bots and you clearly have the knowledge – T. Nielsen Aug 03 '22 at 10:54
  • Why didn't you comment on peteski and Sam answers as well? Those could be comments too. – Parsa99 Aug 03 '22 at 12:17
  • i am a volunteer reviewer, like you can be if you want. top right part when logged in. So the system simply presents new answers and ask for a rating. Not theirs. With Yours i chose to comment and advice how your answer can be a complete answer in itself, or a comment. If You wish. i did not downvote or claim that there is something wrong to correct like other review options. Because i think with your parts a very fine solution can be crafted but the exception numbers is implicit knowledge which put a few words on might make your quick alternative the accepted answer, i aim to help. – T. Nielsen Aug 03 '22 at 13:25
  • 1
    @T.Nielsen: a bit of topic, but as I spend some time looking for the review function, I found out that it requires at least 500 rep :-). So that option is not available for everyone. – Michel Dec 13 '22 at 09:51
2

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);
    }
FireStormHR
  • 37
  • 2
  • 9
  • 2
    Notice 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
-1

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);
    }
}
Desolator
  • 22,411
  • 20
  • 73
  • 96