How to skip invalid fields on SaveChanges - c#

Is it possible to skip invalid values when I'm saving an entity with SaveChanges and save only valid fields?
It's pretty simple to skip on entities level, but I'm not able to find a way of doing that on fields level.

Muhammad's answer gave me great idea. The solution is pretty simple:
try
{
context.Entry(objInDB).State = EntityState.Modified;
context.SaveChanges();
}
catch (Exception ex)
{
var exception = ex as DbEntityValidationException;
if (exception != null)
{
exception.EntityValidationErrors.ToList().ForEach(error =>
{
error.ValidationErrors.ToList().ForEach(validationError =>
{
error.Entry.Property(validationError.PropertyName).IsModified = false;
});
});
context.SaveChanges();
}
}

Related

Entity framework update related entities without concurrency conflict

I have two related entities: User and UserProfile. A user can have many profiles (settings). I want to be able to update them together, but I am currently getting concurrency error when i do so:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
This is my code to update:
public void UpdateUser(UserList user, int timeoutMins)
{
using (var ctx = GetCodingContext())
{
try
{
ctx.Entry(user).State = System.Data.Entity.EntityState.Modified;
CR_USER_PROFILE timeoutProfile = GetTimeoutUserProfile(user.UserGUID);
if (timeoutProfile != null && !timeoutProfile.PROFILE_VALUE.Equals(timeoutMins.ToString()))
{
timeoutProfile.PROFILE_VALUE = timeoutMins.ToString();
UpdateUserProfile(timeoutProfile,ctx);
}
else if(timeoutProfile == null && timeoutMins > 0)
{
var timeoutKey = FFCEnumerations.Profiles.Keys.Timeout.GetStringValue();
AddUserProfile(user, timeoutKey, timeoutMins.ToString(), ctx);
}
ctx.SaveChanges();
}
catch (Exception ex)
{
throw new Exception("Error occurred updating user " + ex);
}
}
}
public void UpdateUserProfile(CR_USER_PROFILE profile, CodingContext ctx)
{
try
{
ctx.Entry(profile).State = System.Data.Entity.EntityState.Modified;
}
catch (Exception)
{
throw new Exception("Error occurred updating User Profile");
}
}
public CR_USER_PROFILE GetTimeoutUserProfile(Guid userGuid)
{
using (var ctx = GetCodingContext())
{
var timeoutKey = FFCEnumerations.Profiles.Keys.Timeout.GetStringValue();
var profileList = ctx.CR_USER_PROFILE.Where(p => p.UserGUID == userGuid && p.PROFILE_TYPE_CD == timeoutKey);
return profileList.SingleOrDefault();
}
}
It works well when I add both entities, but not when updating. Any ideas?
I think this is where there's a lot of discussion on this problem - Entity Framework: "Store update, insert, or delete statement affected an unexpected number of rows (0)."
I figured out that I was using a different context for fetching the profile I wanted to update. This was causing the concurrency conflict because EF thought this entity was being changed somewhere else (another context). So, I created an overload for this method so I can pass the context as an argument and fetch the entity with the same context I was going to update it with.
public CR_USER_PROFILE GetTimeoutUserProfile(Guid userGuid, CodingContext ctx)
{
var timeoutKey = FFCEnumerations.Profiles.Keys.Timeout.GetStringValue();
var profileList = ctx.CR_USER_PROFILE.Where(p => p.UserGUID == userGuid && p.PROFILE_TYPE_CD == timeoutKey);
return profileList.SingleOrDefault();
}

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.

aggregate exceptions with entity framework in dll

At work we are looking to move to an ORM (still using an access database with ADO!) I started building with entity framework and everything was going smoothly until I separated it off into it's own .dll (so we could have the website/crm/production/barcoding systems all using the same database logic).
The issue comes with handling the DbEntityValidationExceptions, my initial test code (which worked)
public override int SaveChanges(System.Data.Objects.SaveOptions options)
{
try{return base.SaveChanges(options);}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Class: {0}, Property: {1}, Error: {2}",
validationErrors.Entry.Entity.GetType().FullName,
validationError.PropertyName,
validationError.ErrorMessage);
}
}
//handle here
throw;
}
}
but this doesn't get caught now and i'm left handling a generic threadException. Is there any way to access the original data (cast the threadException?) or is there a different approach I should take. I think I just need a push in the right direction and I can figure something out.
Regards, Pete
UPDATE:
Hmm bit of an issue calling the GetValidationErrors. I notice that my database Context has the baseClass of ObjectContext and not DbContext (So I can't call the ValidateEntity). I'm using Entity Framework 5 with default code generation enabled - using a database first approach if that helps.
check for validation errors before calling SaveChanges. Something like...
var errors = context.GetValidationErrors();
if(errors.Any())
{
//handle validation errors
}
else
{
context.SaveChanges();
}
from memory, so the exact syntax may not be correct.
this is what I ended up going with in the end (i'll probably end up fleshing it out as I learn more about EF)
public List<DbEntityValidationException> vErrors = new List<DbEntityValidationException>();
public int DbChanges = 0;
public bool SaveChanges()
{
try
{
this.vErrors = (List<DbEntityValidationException>)base.GetValidationErrors();
if (this.vErrors.Count == 0)
{
this.DbChanges = base.SaveChanges();
return true;
}
}
catch (Exception Ex)
{
this.vErrors.Add(new DbEntityValidationException(string.Format("General Error: {0}", Ex.GetType().ToString())));
}
return false;
}
and from code
using(Db db = new Db())
{
//changes
if(db.SaveChanges)
{
//some message using db.DbChanges
}
else
{
//handle errors in db.vErrors
}

Handling duplicate insertion

So I've got this piece of code which, logically should work but Entity Framework is behaving unexpectedly.
Here:
foreach (SomeClass someobject in allObjects)
{
Supplier supplier = new Supplier();
supplier.primary_key = someobject.id;
supplier.name = someobject.displayname;
try
{
sm.Add(supplier);
ro.Created++;
}
catch (Exception ex)
{
ro.Error++;
}
}
Here's what I have in sm.Add()
public Supplier Add(Supplier supplier)
{
try
{
_ctx.AddToSupplier(supplier);
_ctx.SaveChanges();
return supplier;
}
catch (Exception ex)
{
throw;
}
}
I can have records in allObjects that have the same id. My piece of code needs to support this and just move on to the next and try to insert it, which I think should work.
If this happens, an exception is throw, saying that records with dupe PKs cannot be inserted (of course). The exception mentions the value of the PK, for example 1000.
All is well, a new supplier is passed to sm.Add() containing a PK that's never been used before. (1001)
Weirdly though, when doing SaveChanges(), EF will whine about not being able to insert records with dupe PKs. The exception still mentions 1000 even though supplier contains 1001 in primary_key.
I feel this is me not using _ctx properly. Do I need to call something else to sync it ?
Found it, had to change something in the Add() method:
public Supplier Add(Supplier supplier)
{
try
{
_ctx.AddToSupplier(supplier);
_ctx.SaveChanges();
return supplier;
}
catch (Exception ex)
{
_ctx.Supplier.Detach(supplier);
throw;
}
}

how to ignore errors in SaveChange EF4

Please see to example1. If some of the data will be entered incorrectly, EF4 will not survive nor any record.
The question: whether as a force to ignore an error in one record and continue on.
example1:
foreach (var tag in split)
{
context.NameToResourcer.AddObject(new NameToResourcer()
{
id_resource = resource.id,
name = tag
});
}
context.NameToResourcer.AddObject(new NameToResourcer()
{
id_resource = resource.id,
name = ExtractDomainNameFromURL(resource.url)
});
try
{
context.SaveChanges();
}
catch (UpdateException ex)
{
}
catch (Exception ex)
{
throw;
}
example2 alternative:
foreach (var tag in split)
{
try
{
context.NameToResourcer.AddObject(new NameToResourcer()
{
id_resource = resource.id,
name = tag
});
context.SaveChanges();
}
catch (UpdateException ex)
{
}
}
try
{
context.NameToResourcer.AddObject(new NameToResourcer()
{
id_resource = resource.id,
name = ExtractDomainNameFromURL(resource.url)
});
context.SaveChanges();
}
catch (UpdateException ex)
{
}
Context behaves like unit of work. It means that when you modify data and store them with the single call to SaveChanges you are telling EF that you want atomic operation - either all changes are successfully saved or all changes are rolled back. EF use a transaction internally to support this behavior. If you don't want this behavior you cannot save all data with single call to SaveChanges. You must use separate call for each atomic set of data.
One possible solution is to disable validation on saving.But I don't recommend it.
db.Configuration.ValidateOnSaveEnabled = false;

Categories

Resources