How to catch DbUpdateException Unique exception? - c#

I'm inserting a Tag when it already exists into the Tag table, hence it returns the message,
Message "Cannot insert duplicate key row in object 'dbo.Tag' with unique index 'IX_Tag_Name'. The duplicate key value is (Lemon).\r\nThe statement has been terminated." string
I've tried to catch this Unique exception by checking for the Number 2601 but am unable to access the Number property.
Apparently this is how you are suppose to catch the exception but ex.InnerException.InnerException is null, so the switch statement never executes.
How can I catch UniqueKey Violation exceptions with EF6 and SQL Server?
catch (DbUpdateException ex)
{
if (ex.InnerException.InnerException is SqlException sqlException)
{
switch (sqlException.Number)
{
// If the tag already exists
case 2601: // Unique Key violation
}
}
}

Your code is checking the inner exception of your inner exception. This is null, and hence your code is failing. Instead do:
catch (DbUpdateException ex)
{
if (ex.InnerException is SqlException sqlException)
{
switch (sqlException.Number)
{
// If the tag already exists
case 2601: // Unique Key violation
}
}
}

Related

How to handle Unique Key Constraint in Insert or Update? [duplicate]

I want to know how we identify the primary key duplication error from SQL Server error code in C#.
As a example, I have a C# form to enter data into a SQL Server database, when an error occurs while data entry, how can I identify the reason for the error from the exception?
If you catch SqlException then see its number, the number 2627 would mean violation of unique constraint (including primary key).
try
{
// insertion code
}
catch (SqlException ex)
{
if (ex.Number == 2627)
{
//Violation of primary key. Handle Exception
}
else throw;
}
MSSQL_ENG002627
This is a general error that can be raised regardless of whether a
database is replicated. In replicated databases, the error is
typically raised because primary keys have not been managed appropriately across the topology.
This is an old thread but I guess it's worth noting that since C#6 you can:
try
{
await command.ExecuteNonQueryAsync(cancellation);
}
catch (SqlException ex) when (ex.Number == 2627)
{
// Handle unique key violation
}
And with C#7 and a wrapping exception (like Entity Framework Core):
try
{
await _context.SaveChangesAsync(cancellation);
}
catch (DbUpdateException ex)
when ((ex.InnerException as SqlException)?.Number == 2627)
{
// Handle unique key violation
}
The biggest advantage of this approach in comparison with the accepted answer is:
In case the error number is not equal to 2627 and hence, it's not a unique key violation, the exception is not caught.
Without the exception filter (when) you'd better remember re-throwing that exception in case you can't handle it. And ideally not to forget to use ExceptionDispatchInfo so that the original stack is not lost.
In case of Entity Framework, the accepted answer won't work and the error will end up not being caught. Here is a test code, only the entity catch statement will be hit or of course the generic exception if entity statement removed:
try
{
db.InsertProcedureCall(id);
}
catch (SqlException e0)
{
// Won't catch
}
catch (EntityCommandExecutionException e1)
{
// Will catch
var se = e1.InnerException as SqlException;
var code = se.Number;
}
catch (Exception e2)
{
// if the Entity catch is removed, this will work too
var se = e2.InnerException as SqlException;
var code = se.Number;
}
Working code for filter only duplicate primary key voilation exception
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
.........
try{
abc...
}
catch (DbUpdateException ex)
{
if (ex.InnerException.InnerException is SqlException sqlEx && sqlEx.Number == 2601)
{
return ex.ToString();
}
else
{
throw;
}
}
Note fine detial :- ex.InnerException.InnerException not ex.InnerException

How can I catch UniqueKey Violation exceptions with EF6 and SQL Server?

One of my tables have a unique key and when I try to insert a duplicate record it throws an exception as expected. But I need to distinguish unique key exceptions from others, so that I can customize the error message for unique key constraint violations.
All the solutions I've found online suggests to cast ex.InnerException to System.Data.SqlClient.SqlException and check the if Number property is equal to 2601 or 2627 as follows:
try
{
_context.SaveChanges();
}
catch (Exception ex)
{
var sqlException = ex.InnerException as System.Data.SqlClient.SqlException;
if (sqlException.Number == 2601 || sqlException.Number == 2627)
{
ErrorMessage = "Cannot insert duplicate values.";
}
else
{
ErrorMessage = "Error while saving data.";
}
}
But the problem is, casting ex.InnerException to System.Data.SqlClient.SqlException causes invalid cast error since ex.InnerException is actually type of System.Data.Entity.Core.UpdateException, not System.Data.SqlClient.SqlException.
What is the problem with the code above? How can I catch Unique Key Constraint violations?
With EF6 and the DbContext API (for SQL Server), I'm currently using this piece of code:
try
{
// Some DB access
}
catch (Exception ex)
{
HandleException(ex);
}
public virtual void HandleException(Exception exception)
{
if (exception is DbUpdateConcurrencyException concurrencyEx)
{
// A custom exception of yours for concurrency issues
throw new ConcurrencyException();
}
else if (exception is DbUpdateException dbUpdateEx)
{
if (dbUpdateEx.InnerException != null
&& dbUpdateEx.InnerException.InnerException != null)
{
if (dbUpdateEx.InnerException.InnerException is SqlException sqlException)
{
switch (sqlException.Number)
{
case 2627: // Unique constraint error
case 547: // Constraint check violation
case 2601: // Duplicated key row error
// Constraint violation exception
// A custom exception of yours for concurrency issues
throw new ConcurrencyException();
default:
// A custom exception of yours for other DB issues
throw new DatabaseAccessException(
dbUpdateEx.Message, dbUpdateEx.InnerException);
}
}
throw new DatabaseAccessException(dbUpdateEx.Message, dbUpdateEx.InnerException);
}
}
// If we're here then no exception has been thrown
// So add another piece of code below for other exceptions not yet handled...
}
As you mentioned UpdateException, I'm assuming you're using the ObjectContext API, but it should be similar.
In my case, I'm using EF 6 and decorated one of the properties in my model with:
[Index(IsUnique = true)]
To catch the violation I do the following, using C# 7, this becomes much easier:
protected async Task<IActionResult> PostItem(Item item)
{
_DbContext.Items.Add(item);
try
{
await _DbContext.SaveChangesAsync();
}
catch (DbUpdateException e)
when (e.InnerException?.InnerException is SqlException sqlEx &&
(sqlEx.Number == 2601 || sqlEx.Number == 2627))
{
return StatusCode(StatusCodes.Status409Conflict);
}
return Ok();
}
Note, that this will only catch unique index constraint violation.
try
{
// do your insert
}
catch(Exception ex)
{
if (ex.GetBaseException().GetType() == typeof(SqlException))
{
Int32 ErrorCode = ((SqlException)ex.InnerException).Number;
switch(ErrorCode)
{
case 2627: // Unique constraint error
break;
case 547: // Constraint check violation
break;
case 2601: // Duplicated key row error
break;
default:
break;
}
}
else
{
// handle normal exception
}
}
// put this block in your loop
try
{
// do your insert
}
catch(SqlException ex)
{
// the exception alone won't tell you why it failed...
if(ex.Number == 2627) // <-- but this will
{
//Violation of primary key. Handle Exception
}
}
EDIT:
You could also just inspect the message component of the exception. Something like this:
if (ex.Message.Contains("UniqueConstraint")) // do stuff
I thought it might be useful to show some code not only handling the duplicate row exception but also extracting some useful information that could be used for programmatic purposes. E.g. composing a custom message.
This Exception subclass uses regex to extract the db table name, index name, and key values.
public class DuplicateKeyRowException : Exception
{
public string TableName { get; }
public string IndexName { get; }
public string KeyValues { get; }
public DuplicateKeyRowException(SqlException e) : base(e.Message, e)
{
if (e.Number != 2601)
throw new ArgumentException("SqlException is not a duplicate key row exception", e);
var regex = #"\ACannot insert duplicate key row in object \'(?<TableName>.+?)\' with unique index \'(?<IndexName>.+?)\'\. The duplicate key value is \((?<KeyValues>.+?)\)";
var match = new System.Text.RegularExpressions.Regex(regex, System.Text.RegularExpressions.RegexOptions.Compiled).Match(e.Message);
Data["TableName"] = TableName = match?.Groups["TableName"].Value;
Data["IndexName"] = IndexName = match?.Groups["IndexName"].Value;
Data["KeyValues"] = KeyValues = match?.Groups["KeyValues"].Value;
}
}
The DuplicateKeyRowException class is easy enough to use... just create some error handling code like in previous answers...
public void SomeDbWork() {
// ... code to create/edit/update/delete entities goes here ...
try { Context.SaveChanges(); }
catch (DbUpdateException e) { throw HandleDbUpdateException(e); }
}
public Exception HandleDbUpdateException(DbUpdateException e)
{
// handle specific inner exceptions...
if (e.InnerException is System.Data.SqlClient.SqlException ie)
return HandleSqlException(ie);
return e; // or, return the generic error
}
public Exception HandleSqlException(System.Data.SqlClient.SqlException e)
{
// handle specific error codes...
if (e.Number == 2601) return new DuplicateKeyRowException(e);
return e; // or, return the generic error
}
If you want to catch unique constraint
try {
// code here
}
catch(Exception ex) {
//check for Exception type as sql Exception
if(ex.GetBaseException().GetType() == typeof(SqlException)) {
//Violation of primary key/Unique constraint can be handled here. Also you may //check if Exception Message contains the constraint Name
}
}
You have to be very specific while writing the code.
try
{
// do your stuff here.
{
catch (Exception ex)
{
if (ex.Message.Contains("UNIQUE KEY"))
{
Master.ShowMessage("Cannot insert duplicate Name.", MasterSite.MessageType.Error);
}
else { Master.ShowMessage(ex.Message, MasterSite.MessageType.Error); }
}
I have just updated the above code a bit and its working for me.

SqlException The INSERT statement conflicted with the FOREIGN KEY constraint

The INSERT statement conflicted with the FOREIGN KEY constraint FK_Kupovina_Kupac. The conflict occurred in database OnlineApoteka, table dbo.Kupac, column 'ID'.
The statement has been terminated.
I'm really confused I do not know why we do not allow? I reported a bug, in throw ex; ?
public static void Kupi(long lekID, int kolicina, double cena, long nacinIsporukeID, string korisnickoIme)
{
SqlConnection con = new SqlConnection();
try
{
con.ConnectionString = ConfigurationManager.ConnectionStrings["OnlineApotekaConnectionString"].ConnectionString;
con.Open();
string updateLager = #"
UPDATE Lager
SET Kolicina=Kolicina-#Kolicina
WHERE LekID=#LekID";
SqlCommand cmd = new SqlCommand(updateLager, con);
cmd.Parameters.AddWithValue("#LekID", lekID);
cmd.Parameters.AddWithValue("#Kolicina", kolicina);
cmd.ExecuteNonQuery();
string insertIntoKupovina=#"
INSERT INTO Kupovina (KupacID, LekID, Datum, Kolicina, Cena, NacinIsporukeID)
VALUES (#KupacID, #LekID, #Datum, #Kolicina, #Cena, #NacinIsporukeID)";
cmd = new SqlCommand(insertIntoKupovina, con);
cmd.Parameters.AddWithValue("#KupacID", KupacAdapter.GetID(korisnickoIme));
cmd.Parameters.AddWithValue("#LekID", lekID);
cmd.Parameters.AddWithValue("#Datum", DateTime.Now.Date);
cmd.Parameters.AddWithValue("#Kolicina", kolicina);
cmd.Parameters.AddWithValue("#Cena", cena);
cmd.Parameters.AddWithValue("#NacinIsporukeID", nacinIsporukeID);
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
throw ex;
}
finally
{
con.Close();
}
}
I have a routine event for the button Buy.
When you click Kupi, should be removed from the table Lager volumes and placed in the Kupac Kupovina
protected void kupiButton_Click(object sender, EventArgs e)
{
KupovinaAdapter.Kupi(Convert.ToInt64(kupovinaGreedView.SelectedDataKey["LekID"].ToString()),
Convert.ToInt32(kolicinaTextBox.Text),
Convert.ToInt64(kupovinaGreedView.SelectedDataKey["Cena"].ToString()),
Convert.ToInt64(nacinIsporukeDropDownList.SelectedValue),
User.Identity.Name);
}
protected void kupovinaGreedView_SelectedIndexChanged(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(kupovinaGreedView.SelectedDataKey["Lek"].ToString()))
{
LekLabel.Text = kupovinaGreedView.SelectedDataKey["Lek"].ToString();
}
if (!String.IsNullOrEmpty(kupovinaGreedView.SelectedDataKey["Kolicina"].ToString()))
{
kolicinaValidator.MaximumValue = kupovinaGreedView.SelectedDataKey["Kolicina"].ToString();
}
The reason you are getting the error is because of how the database schema is defined. In particular the table Kupac is used in a relation with the table Kupovina such that the KupacID in Kupovina must match a value from the ID field in the Kupac table.
Because of this you cannot insert records into the Kupovina table using a KupacID that doesn't already exist in the Kupac table - it's a Foreign-Key Constraint violation.
You have two options:
make sure you first insert a record into the Kupac table
drop the foreign-key constraint from the database schema if that makes business-sense
But you should examine the output of the KupacAdapter.GetID(korisnickoIme) which is what is supposed to provide the KupacID value. If this method for some reason does not return a valid ID (one from the Kupovina table) than the insert will fail as explained above.
In case you are wondering why the Exception is not begin "handled" than that is because of the code in your catch block. You are basically taking the exception and re-throwing it which is sort of pointless. You might as well just not have a catch block at all..
try {
// ...
}
catch (Exception ex) {
throw ex; // <--- this just re-throws the same exception that was caught!
}
finally {
}
Is, for the most part, nearly equivalent to:
try {
// ...
}
finally {
}
The difference is only in how much of the stack trace is preserved along with the exception, but the exception is bubbled up nonetheless.
The Kupac table does not have the KupacID value in that table that you are trying to insert into the Kupovina table
The KupacID column in the Kupac table is the primary key, while the KupacID column in the Kupovina table is the foreign key pointing back to the primary key
Read up on Foreign Keys
Yes, you are catching the key violation with catch (Exception ex) - however you then rethrow the Exception there.
Do you have an error handler in the calling routine?
throw ex - does not "handle" an error - it is simply raising another (although, the same one in this case - the one that has been caught)
If you are expecting this specific error then you should catch this specific error by catch (SqlException ex) - and then check for this particular error. If it is not this error then throw ex; back up the call-stack... If it is this error then you can ignore (but it would be better to just avoid this kind of INSERT in the first place...)

entity framework 4.4: how to catch the Sql Exception?

I have a table in the database that has the users of the application. The user name is unique, so I have a unique constraint in the table.
I am doing probes and one is try to use the same user name for two users. When the error occurs, I catch the exception "Eception" and I can see that the catched exception is System.Data.Entity.Infraestructure.DbUpdateConcurrencyException and the inner exception is System.Data.OptimisticConcurrencyException.
Then I catch the DbUpdateConcurrencyException, the innter exception is the OptimisticConcurrencyException, so I try to catch this exception too.
If I try to catch the OptimisticConcurrencyException before the DbUpdateConcurrencyException, is not catch, is catch the DbUpdateConcurrencyException.
SO I don't know how I can to catch the SqlException, because I would like to catch the error of the Sql Server, to get the code.
Thanks.
You can't handle 'inner exceptions'. You need to inspect the actual exception thrown in your catch handler, and rethow if you can't handle it. Example:
try
{
}
catch (DbUpdateConcurrencyException ex)
{
if (CanHandleException(ex))
{
// do what you have to do to handle the exception
}
else
{
throw; // can't handle this exception - just let it bubble up
}
}
In the method CanHandleException you would write the logic that determines whether you can handle this exception and do something meaningful (perhaps retry the operation). You do this by inspecting the properties of the exception (message, InnerException, ...)
Have you tried:
try
{
}
catch (SqlException ex)
{
}
You can handle it use
var objContext = ((IObjectContextAdapter)ctx).ObjectContext;
var entry = dbUpdateConcurrencyException.Entries.Single();
if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Detached;
}
else
{
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
objContext.Refresh(RefreshMode.ClientWins,dbUpdateConcurrencyException.Entries.Select(e => e.Entity));
}

How can I know if an SQLexception was thrown because of foreign key violation?

I want to tell the user that a record was not deleted because it has child data, but how can I be sure that the exception was thrown because of a foreign key violation? I see that there a sqlexception class that is used for all sql exception.
Assume you're using SQL Server.
Using teh web archive - https://web.archive.org/web/20190120182351/https://blogs.msdn.microsoft.com/tomholl/2007/08/01/mapping-sql-server-errors-to-net-exceptions-the-fun-way/
try
{
# SQL Stuff
}
catch (SqlException ex)
{
if (ex.Errors.Count > 0) // Assume the interesting stuff is in the first error
{
switch (ex.Errors[0].Number)
{
case 547: // Foreign Key violation
throw new InvalidOperationException("Some helpful description", ex);
break;
case 2601: // Primary key violation
throw new DuplicateRecordException("Some other helpful description", ex);
break;
default:
throw new DataAccessException(ex);
}
}
}
Case 547 is your man.
UPDATE The above is sample code and should not be used. Please follow the link as to explain why.
You can write your exception-expected code in the Try block if any exception will be thrown it will be catch further now you can get error number.now can check is it a Foreign Key violation or not
try
{
//your deletetion code
}catch (SqlException ex)
{
if (ex.Errors.Count > 0) // Assume the interesting stuff is in the first error
{
switch (ex.Errors[0].Number)
{
case 547: // Foreign Key violation
lblError.Text = "Cannot Delete this Record this is associated with other record...!";
break;
default:
throw;
}
}
}

Categories

Resources