Unable to re-insert after unique key violation - entity-framework-core

I have the following scenario:
A business logic function that uses EF Core2 checks if a record already exists. If the record does not exists, it is inserted on the database.
I have multiple threads calling this business logic function. What happens is:
If the function is called simultaneous with the same parameters, both instances checks if the record exists - and it does not exists. So both instances inserts the same record.
When context.SaveChanges() is called on the first instance, all goes ok. But the second SaveChanges() throws an exception because the record already exists (there is an unique index on the database).
If i catch that exception and try to insert with new value for UNIQUE_KEY, still it's throwing an exception since previously added entity still in track.
How can I implement this to avoid the exception?

Instead of simply adding a new entity inside your catch-block, you could modify the existing entity that caused the exception.
foreach (var entry in _dbContext.ChangeTracker.Entries<YourEntity>().Where(e => e.State == EntityState.Added)))
{
entry.Entity.YourProperty = newvalue;
}
Just iterate through all your entities of given type and update your property that has a unique constraint.

Related

"Cannot insert explicit value for identity column in table 'x' when IDENTITY_INSERT is set to OFF" - inserting record with nested custom object

I get the error "Cannot insert explicit value for identity column in table 'UserPermission' when IDENTITY_INSERT is set to OFF" trying to insert a record as follows:
dbContext.User.Add(someUser);
dbContext.SaveChanges();
That being said, the User file has the custom class UserPermission as one of its parameters, and someUser's UserPermission is not null and has a set ID parameter. Why does this happen and is it possible to avoid getting this error without having to explicitly add a UserPermissionID foreign key parameter in my User model and setting the UserPermission parameter to null?
Thanks in advance.
This issue typically happens when deserializing entities that have related entities in the object graph then attempting to add them. UserPermission is likely an existing record that in the DB is set up with an identity PK, but EF doesn't appear to recognize that in the entity definition. (I.e. set to DatabaseGenerated(DatabaseGeneratedOption.Identity). If it had been you would most likely be seeing a different problem where a completely new duplicate UserPermission was being created.
If someUser, and it's associated someUser.UserPermission are deserialized entities then you need to do a bit of work to ensure EF is aware that UserPermission is an existing row:
void AddUser(User someUser)
{
var existingPermission = _context.UserPermissions.Local
.SingleOrDefault(x => x.UserPermissionId == someUser.UserPermission.UserPermissionId);
if (existingPermission != null)
someUser.UserPermission = existingPermission;
else
_context.Attach(someUser.UserPermission);
_context.Users.Add(someUser);
_context.SaveChanges();
}
In a nutshell, when working with detached entities that a DbContext may not be tracking, we need to check the Local state for any existing tracked instance for that ID. If we find one, we substitute the detached reference for the tracked one. If we don't find one, we attach the detached one before Adding our user.
This still isn't entirely safe because it assumes that the referenced UserPermission will exist in the database. If for any reason a non-existent UserPermission is sent in (row deleted, or fake data) you will get an exception on Save.
Passing detached entity references around can seem like a simple option at first, but you need to do this for every reference within a detached entity. If you simply call Attach without first checking, it will likely work until you come across a scenario where at runtime it doesn't work because the context happens to already be tracking an instance.

Store update insert or delete statement affected an unexpected number of rows (0)

I'm using the code show below to update an entity model. But I get this error:
Store update, insert, or delete statement affected an unexpected number of rows (0)
And reason of this error is also known, it's because the property does not exist in the database. For that I found have only one option: first check that the entity exists, then update it.
But, as I'm updating 10,000+ rows at a time, it will be time consuming to check each time in database if this property exist or not.
Is there any another way to solve this ?
Thank you.
foreach (Property item in listProperties)
{
db.Properties.Attach(item);
db.Entry(item).Property(x => x.pState).IsModified = true;
}
db.SaveChanges();
You use it the wrong way. If you want to update without retrieving the entity, just change the state of the updated entity with providing the id.
foreach (Property item in listProperties)
{
db.Entry(item).State = EntityState.Modified;
}
db.SaveChanges();
Attaching an existing but modified entity to the context
If you have an entity that you know already exists in the database but
to which changes may have been made then you can tell the context to
attach the entity and set its state to Modified.
When you change the state to Modified all the properties of the entity
will be marked as modified and all the property values will be sent to
the database when SaveChanges is called.
Source
I got this error Just by updating EF version from 5 to 6 solved the issue.

SaveChangesAsync fails and callback not executed (IdeaBlade)

I inherited an application built on Silverlight 4 using IdeaBlade from DevForce 2010 version 6.1.15.0. The backend database is SQL Server 2008. In tracking down some updates/inserts that weren't working, I discovered that I was getting the following error from the call to IdeaBlade.EntityModel.EntityManager.SaveChangesAsync().
"An entity or entities containing a referenced temporary Id is missing from the list of entities provided. Missing entities include: Revision: -100. See exception members for more details"
Additional digging uncovered the following additional information.
"The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_valuation_revision". The conflict occurred in database "XX", table "Revision", column 'RevisionID'. The statement has been terminated."
In digging deeper, I discovered the following:
The two main classes involved are: Valuation and Revision
Valuation has a collection of Revisions. On the database, this is represented by a FK on the Revision table (not null) that references the PK of the Valuation table.
In addition, Valuation has a reference to the CurrentRevision. This is represented on the database as a FK on the Valuation table (can be null if there are no revisions) that references the PK on the Revision table.
In the problem code, a new Revision object is created like so:
myNewRevision = new Revision { Valuation = myExistingValuation };
At this point, myNewRevision is an IdeaBlade.EntityModel.Entity with an EntityState of "Added".
After a couple of additional changes to the object, the following code is executed.
EntityManager.AddEntity(myNewRevision);
The above call to AddEntity doesn't seem to accomplish anything as the state of MyNewRevision is the same after the call as it is before the call.
Next an attempt is made to save the newly created Revision object.
SaveChangesAsync(new[] { myNewRevision }, null, RevisionCallback, null);
The above statement is within a try...catch block and no error is thrown, but the RevisionCallback routine is never executed. Also the EntityState of myNewRevision is still "Added" and the key is still showing as -100.
Then the existing Valuation object is updated with the reference to the new Revision.
myExistingValuation.CurrentRevision = myNewRevision;
This causes the EntityState of myExistingValuation to change from "Unchanged" to "Modified".
Finally, the code tries to save the changes to the Valuation object.
SaveChangesAsync(new[] { myExistingValuation }, null, ValuationCallback, null);
The result of this call is that the ValuationCallback is called, but myExistingValuation is still showing as "Modified", the reference to myExistingValuation.CurrentRevision still refers to myNewRevision with a key of -100 and an EntityState of "Added". The EntitySaveOperation object returned to the callback method has Exception == null, HasError = true, and the error is the one described at the beginning of this question. That is,
"An entity or entities containing a referenced temporary Id is missing from the list of entities provided. Missing entities include: Revision: -100. See exception members for more details"
and
"The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_valuation_revision". The conflict occurred in database "XX", table "Revision", column 'RevisionID'. The statement has been terminated."
The database is not updated.
Any suggestions? Why doesn't the callback routine from the first call to SaveChangesAsync get executed? How can I get the updates to work?
It's odd that the RevisionCallback isn't called, but it's possible the async method hasn't completed when you do the next steps.
You can't use try/catch with the async methods here, as DevForce 2010 doesn't use the newer task-based async pattern; so exceptions will be returned to your callback or completion handler, or possibly to an EntityServerError handler if one is set up for the EntityManager.
DevForce will by default save all changes to entities in cache unless you specify a save list, but that list doesn't have to contain only one item. When you do a save of entities with temporary ids, all entities with a temporary PK or FK need to be in the save list passed to SaveChangesAsync. DevForce will automatically handle the fixup to the FK after the save completes successfully.
Is there any reason you can't do something like the following instead:
myNewRevision = new Revision { Valuation = myExistingValuation };
myExistingValuation.CurrentRevision = myNewRevision;
SaveChangesAsync(ValuationCallback);

Avoid inserting duplicate records in Entity Framework

I have a typical scenario where users enter data that is inserted into a SQL database using Entity Framework 6.0. However, some rows that are part of the entity need to be unique (already enforced with unique key constraints in the database).
To avoid possible concurrency or performance issues I favour these checks to be left done by SQL Server.
When attempting to save a new entity that holds a duplicate row, a DbUpdateException is thrown by Entity Framework. The inner exception is a SqlException with its Number equal to 2627, and a message that reads:
"Violation of UNIQUE KEY constraint 'UK_MyTable_MyRule'. Cannot insert duplicate key in object 'dbo.MyTable'".
Considering that there are several tables involved, which may each have their own unique constraints defined, is there no better way to conclude a friendlier message to the user that reads:
"A MyEntity with the name 'MyEntity1' already exists."
...without having to infer this through the Number and Message properties from the SqlException?
For example:
try
{
...
context.SaveChanges();
}
catch (DbUpdateException exception)
{
var sqlException = exception.InnerException as SqlException;
bool isDuplicateInMyTable3 =
sqlException != null &&
sqlException.Number = 2627/*Unique Constraint Violation*/ &&
sqlException.Message.Contains("'UK_MyTable3_");
if (isDuplicateInMyTable3)
{
return "A MyTable3 with " + ... + " already exists.";
}
throw exception;
}
Is there a "cleaner" way to achieve the same that does not involve looking through the error message string?
You may like to enjoy the AddOrUpdate method.
Research it first. I have noted experts warning of over zealous use.
Context.Set<TPoco>().AddOrUpdate(poco);
can still throw other EF\DB exceptions.
But Duplicate primary key should not be one of them.
Other constraint issues are as before.

Deleting all related entities in Entity Framework

I have an entity called Entry connected to multiple TimeWindows. I want to clear all time windows, then add new ones. At first I tried:
target.TimeWindows.Clear();
but this didn't really delete them, and only tried to remove the relationship, which caused an exception since there is foreign key from TimeWindows to Entry. Then I thought I should do this:
foreach (var tw in target.TimeWindows)
context.DeleteObject(tw);
but this throw an exception as well, since the collection was modified inside the foreach statement. So I thought of this:
while (target.TimeWindows.Count > 0)
context.DeleteObject(target.TimeWindows.Last());
But now I am a bit concerned about using Count property, because it might cause a SQL SELECT COUNT statement to be executed. Does it? If yes, how can I delete all time windows in Entity Framework?
Calling count on navigation property will cause select only if lazy loading is enabled and the property is not loaded yet. So the first call can cause something like:
SELECT * FROM TimeWindows WHERE TargetId = #targetId
and all count evaluations will just execute on loaded data.
You can also use this to avoid the second exception:
foreach (var tw in target.TimeWindows.ToList())
context.DeleteObject(tw);
Or you can change your database and model to support identifying relation (FK to parent will became part of TimeWindow's PK) and in such case your first code snippet will work.