Cloning entity fails with primary-key violation on SaveChanges() - c#

I'm trying to copy an entity like described here. In my entity-wrapper base-class I have the following code to copy/clone an entity.
public TBaseEntityModel Clone(TPrimaryKey newPrimaryKey)
{
var newEntity = Activator.CreateInstance<TEntity>();
var clone = DbContext.Entry(newEntity);
clone.State = EntityState.Added;
DbContext.Entry(newEntity).CurrentValues.SetValues(TheEntity);
clone.State = EntityState.Detached;
var cloneEntityModel= (TBaseEntityModel)Activator.CreateInstance(typeof(TBaseEntityModel), DbContext, newEntity);
cloneEntityModel.PrimaryKeyValue = newPrimaryKey;
return cloneEntityModel;
}
After I call the Clone-method on my concrete entity, it has also it's new Primary Key set to the given value of newPrimaryKey.
The propblem occurs when I call SaveChanges() on the underlying context.
It then throws:
Violation of PRIMARY KEY constraint '...'. Cannot insert duplicate key in object 'dbo....'. The duplicate key value is (553a7aa9-0ac2-40a0-820f-43a3b4af745f).
But when I look at my clone, the PK is set to another value.
So I guess it is something inside the ObjectContext or even deeper inside.
But I have no idea how to get away the error.

I suspect that the database is trying to generate the primary key, but you are also trying to specify it explicitly before saving. If you want to specify values for identity columns then you need to jump through some hoops (How can I force entity framework to insert identity columns?). Otherwise, just don't set the value and it will be generated for you when you perform the insert.

Related

Microsoft Entity Framework Core: Cannot insert duplicate key in object

I am trying to update a parent entity, GuildMemberTeam, with child entities, GuildMember, Team and GuildMemberChallenge which also has a child entities, GuildMember and Challenge but am getting the following inner exception:
Inner Exception 1: SqlException: Violation of PRIMARY KEY constraint
'PK_Challenge'. Cannot insert duplicate key in object 'dbo.Challenge'.
The duplicate key value is (15ae8798-8567-457b-812a-5820cf7843e5). The
statement has been terminated.
The only new entity is the GuildMemberTeam as all the others already exist, but these are checked and recreated as follows:
public void AddChallenge(Challenge challenge)
{
if (challenge != null)
{
var id = challenge.Id == default(Guid) ? Guid.NewGuid() : challenge.Id;
Challenge = new Challenge(id, challenge.Name, challenge.Phase, challenge.Type, challenge.Star, challenge.Gear, challenge.Level, challenge.Reward);
}
}
This works for all the other entities apart from Challenge where i get the error. Can anyone please help me understand what i am doing wrong.
It doesn't change the fact that the problem is that you are trying to insert the same row twice (same Guid=Id) into the dbo.Challenge table.
This might be due to a debugging issue or something. You can either delete the row from the table with a
DELETE FROM [Challenge] WHERE Id = '15ae8798-8567-457b-812a-5820cf7843e5' and try running the app again.
If this doesn't solve your problem your entity management is faulty and you have to revise the ID handling. Implement ID checking before you try to save your context or something like that.
The other issue might be that your classes are not defined properly and EF doesn't recognize the relations. The relationships you are talking about are not parent-child, they are either one-to-many, many-to-many, many-to-one or none. DB RELATIONS
Each of your POCO-s should contain and instance of the other class, thus you define a relationship. E.g. if your GuildMemberChallenge contains an IEnumerable and a property with type of challenge.
If none of the above are a solution I need some more code (your classes, the repository) to figure it out.
Update:
When you are adding a new GuildMemberChallenge, which I assume you are trying to do now. You should set it's Challenge property to an existing entity if it exists, if it doesn't you can create one, but at the moment you are trying to create a Challenge that already exists in the database.
You are creating new Challenge but pass id of existing Challenge if it is set.
var id = challenge.Id == default(Guid) ? Guid.NewGuid() : challenge.Id;
I think you, that if you create new entity you should always create new Id
var id = Guid.NewGuid();

Is this an EF Core bug or an I doing it wrong to update one entity?

I'm using Entity Framework Core together with the repository pattern. To help me out, I coded one base repository with the basic CRUD methods. The update method is as follows:
public void Update(TEntity entity)
{
var contextEntry = _context.Entry<TEntity>(entity);
if (contextEntry.State == EntityState.Dettached)
{
_context.Attach(entity);
}
contextEntry.State = EntityState.Modified;
_context.SaveChanges();
}
Given the BaseRepository class containing this method, I created one User repository inheriting from this
public class UserRepository : BaseRepository<User>, IUserRepository
{
}
And I've used this in the PUT method of one Web API coded with ASP.NET Core
[HttpPut("~/api/users/{id}")]
public IActionResult Put(int id, [FromBody] User user)
{
if (user == null || user.UserId != id)
{
return BadRequest();
}
userRepository.Update(user);
return new NoContentResult();
}
Now, when issuing a request, I get one error in the _context.Attach(entity) line. The exception says that it can't add the entity for tracking because there is already another entity with the same key being tracked.
When debugging I saw that contextEntry.State was set to Unchanged. Hence, it is obviously not equal to EntityState.Dettached. Still, the execution got inside the if statement and tried to attach the entity.
Something is quite wrong here. Is this a bug? Or am I doing something very wrong? I believe that I'm the one doing something very wrong with this update strategy, but I'm unsure about it. In that case, what is wrong with my approach?
EDIT: I updated the Update method to use just _context.Update(entity) and after _context.SaveChanges(). Still, the _context.Update(entity) throws one InvalidOperationException with this message:
Additional information: The instance of entity type 'User' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
You are getting same entity from database some where in the project that's why it give you error.
In Update method you just add the entity in the context that's why you get contextEntry.State Unchanged.
You can fix this problem in two ways.
You need to call the Detach method on same entity when you get it from database.
copy the values from entity which you received in Update method to the existing context entity and save that entity in database.
All the information is in the exception message... you already have another copy of the entity with that primary key attached.
I would recommend one of the following (preferred first):
Use a new Context for each action, don't have a long-lived repository/context
Use .Set<TEntity>.Find(object[] key) on your context using the Primary Key, in order to retrieve any entity you already have.
In your current update method, use the Set<TEntity>.Local.Find(..) to check if it already exists

Why won't EF persist a NULL foreign key to the DB?

I've searched... I promise. The closest I came to an answer is a post where EF self tracking entities are used and I don't understand how to make it apply to my situation.
In my case, I have an existing record where I want to change a value from an int to a NULL.
Table Pseudocode:
PICKLIST_VALUE
Picklist ID (int, PK, Identity, NOT NULL)
PicklistValue (String, NOT NULL)
PERSON
Person_ID (int, PK,Identity, NOT NULL)
Person_Name (varchar(100), NOT NULL)
Person_Prefix (int, FK, NULL)
Person_Suffix (int, FK, NULL)
FOREIGN KEY (Person_Prefix) REFERENCES PICKLIST_VALUES.Picklist_ID
FOREIGN KEY (Person_Suffix) REFERENCES PICKLIST_VALUES.Picklist_ID
I am using EF6, DB first... With a DAL based on Magnus Montin's post at  https://blog.magnusmontin.net/2013/05/30/generic-dal-using-entity-framework/
In my code I have POCO definitions for each entity (table). A Person can have a prefix (Mr., Mrs., etc.) and a Person can have a suffix (Jr., Sr., etc.) If I accidentally set a suffix and then realize that it's incorrect, I want to be able to remove the suffix:
someperson.Person_Suffix = null; // Set FK property to null
someperson.PICKLIST_VALUE = null; // Set navigation property to null
someperson.EntityState = EntityStates.Modified;
DAL.UpdatePerson(someperson);
The UpdatePerson method resolves to this bit based on the DAL article:
public virtual void Update(params T[] items)
{
using (var context = new Entities())
{
DbSet<T> dbSet = context.Set<T>();
foreach (T item in items)
{
dbSet.Add(item);
foreach (DbEntityEntry<IEntity> entry in context.ChangeTracker.Entries<IEntity>())
{
IEntity entity = entry.Entity;
entry.State = GetEntityState(entity.EntityState);
}
}
context.SaveChanges();
}
}
protected static System.Data.Entity.EntityState GetEntityState(CB.DomainModel.EntityState entityState)
{
switch (entityState)
{
case DomainModel.EntityState.Unchanged:
return System.Data.Entity.EntityState.Unchanged;
case DomainModel.EntityState.Added:
return System.Data.Entity.EntityState.Added;
case DomainModel.EntityState.Modified:
return System.Data.Entity.EntityState.Modified;
case DomainModel.EntityState.Deleted:
return System.Data.Entity.EntityState.Deleted;
default:
return System.Data.Entity.EntityState.Detached;
}
}
I don't know enough of what's going on under the covers of the EF stuff and what the .edmx and T4 template code does. If I did, I could probably figure this out. But if I change the values from one int to another, the changes are persisted to the database after this call. But if I null the values, as in this example, to "clear" the foreign key reference, the original value comes back.
It must have something to do with the way the object registers with the ChangeTracker once it's added to the dbSet. Prior to that, it's just a plain old object.
So my question is why is the ChangeTracker (or whatever bit of auto-generated code) allowing the FK to change from one int value to another, but it won't persist the null? And how do I "fix" it or work around the issue?
Thanks.
J
UPDATE:
The key is definitely nullable. Both in the DB and in the class definition. The link to the other post seems to be a different scenario... They want to automatically null the foreign key when the foreign entity is deleted. (I will take a closer look, just to be sure there isn't an answer there, but it seems to be a different issue.) Just for fun I created a new object and copied the properties over, with the exception of the nav properties and set the FK property I want to remove to null and that did work. The null FK was persisted, and both the nav prop and the foreign key prop were null. So it has something to do with the change tracker and how it is maintaining a reference to the entity object, despite all the POCO-ness of it. So, at the very least, I have a work-around. But I would like to understand what's actually happening and how to address it.
SOLUTION:
I just discovered, this morning, that a change I made to the T4 template must have been over-ridden, at some point, and I lost a line setting ProxyCreationEnabled to false for the DBContext...  Once I realized that and put the fix in, I was able to persist the changes properly.  So, in effect, I was fighting something in the change tracker since it was creating the dynamic proxies for the entities retrieved from the DB.  (I think.)
I just discovered, this morning, that a change I made to the T4 template must have been over-ridden, at some point, and I lost a line setting ProxyCreationEnabled to false for the DBContext...  Once I realized that and put the fix in, I was able to persist the changes properly.  So, in effect, I was fighting something in the change tracker since it was creating the dynamic proxies for the entities retrieved from the DB.  (I think.)

Updating foreign key in Entity Framework 7

I am trying to update a foreign key using Entity Framework 7. But it is giving error: The property 'Y' could not be found in object 'X'. I have tried many different solution but still not working. The sample code:
class X
{
property Y {get; set;} -> property Y is a foreign key and also a complex type
}
In table 'X' we have a column 'Y_ID' which is the foreign key.
Note: I just want to update the foreign key. E.g. Initially class 'X' is pointing to 'NULL', I want to update class 'X' to point to 'Y1'
The Entity Framework 7 code:
var x = this.GetX();
this.mainContext.Xs.Attach(x);
var xEntry = this.mainContext.Entry(x);
xEntry.Property("Y").CurrentValue = "Y1"; // Error at this line
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
Detailed Error:
The property 'Y' on entity type 'X' could not be found. Ensure that the property exists and has been included in the model.
Edit
The approach Fabien suggested in his comment works fine. But the problem is we only know about which property to update is at runtime. If I use reflection to achieve this, the problem is entity framework treats the object as new and tries to create it (INSERT) and then throws Primary Key violation (No duplicate entries allowed)
So, is there a way where I can't still update an object property which acts like a foreign key in EF? (I don't know exact property at compile time).
If you get the entities "X" and "Y" from your context, then they're automatically tracked by the ChangeTracker. So if you assign "Y" property of the "X" object with an "Y" instance retrieved from your context and call SaveChanges or SaveChangesAsync, EntityFramework will automically do the stuff for you.
var x = this.GetX();
x.Y = "Y1";
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
By convention, your property "Y" on object "X" should be virtual to indicate that it's an foreign key.
Edit 1 :
If I understand correctly, you want to update properties of your object dynamically at runtime, with values that comme from a web api.
1st way :
Like you did, you can attach your "X" object to your context instance to begin tracking of the entity with EntityState.Unchanged, and then flag each property that need to be updated :
this.mainContext.Xs.Attach(x);
var entry = this.mainContext.entry(x);
entry.Property(p => p.Y).CurrentValue = "Y1";
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
When attaching an entity, you can specify the GraphBehavior, it tell EntityFramework if navigation properties should traversed or not.
2nd way :
Using the DbSet.Update() method :
this.mainContext.Xs.Update(x);
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
It's automatically begin tracking of the entity with the state EntityState.Modified, all properties will be marked as modified. You should watch out when using this method, because all properties will be updated, if some of them are not inititialized in your "X" object, you could lost some data. To prevent that case, you should always validate inputs.
If you want to keep your domain models de-coupled form any ORM, then you should think to separate entity types and domain types. You can use an object mapper like Automapper to map entity to domain type and vice versa. In that way you clearly separate what you do at data access layer and business logic layer.

A referential integrity constraint violation occurred

I'm trying to update an existing entity.
I have the following code:
public MamConfiguration_V1 Save(MamConfiguration_V1 item)
{
mMaMDBEntities.MamConfiguration_V1.Attach(item);
mMaMDBEntities.ObjectStateManager.ChangeObjectState(item, System.Data.EntityState.Modified);
mMaMDBEntities.SaveChanges();
return item;
}
But the Attach methods throws an exception:
A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
How can I fix this?
Seems like you have some relationship with foreign key field and a navigation property in the item, and those fields have conflicting values. This occurs when you load an entity and its related entities, change the relationship at one end, mark only that end as Modified and attempt to save. Make sure you modify relationship at both ends and mark all the affected entities as Modified before calling SaveChanges.
I encountered this exception under a different set of circumstances, and am posting here since this question comes up when the error message is searched.
The exception was thrown when calling IObjectContextAdapter.ObjectContext.AttachTo(entitySetName, entity) with a partially-loaded entity. The foreign keys on the entity were defined, but the navigational properties were not loaded. (That is, O.ItemID had a value, but O.Item was null). The specific circumstances did not allow O.Item to be loaded.
The problem turned out to be that the Object State Manager had loaded the object in a separate method and was already tracking the object defined with the same keys. Since the separate method did not need to track the object state, the issue was resolved by calling IQueryable.AsNoTracking() within that method.
What is the definition of the item object? It seems that in some of its collections that set the realionship with other entities exist some type of conflict. You could try to clear all the collections to see if the problem persists, but in this case you lost the foreign key assignment. But perhaps it could help you to locate the problem.
This could be a tip. When I try to attach an existing entity to the context, I use to do the following:
mMaMDBEntities.Entry<MamConfiguration>(item).State = System.Data.EntityState.Modified;
You can add the using of System.Data to avoid the needed to write it all the time.
This attach the entity in the state that you want, modified in this case and track the changes. This is one line instead of two.
The issue for me was that entity framework had loaded my object in multiple places, so when I updated a foreign key, there were now two references to the same object, one with a foreign key pointing to record a and one with a foreign key pointing to record b, which caused an error since my relationship is one to one. To resolve it, I used context.Entry(Object).State = EntityState.Detached, reloaded the object, made the foreign key change and then saved my changes
Lets say you have the following schema:
If you want to edit the CurrentLocationId in Person, you also need to edit the CurrentLocation object embedded in the Person object. EF will automatically populate the CurrentLocation object because CurrentLocationId has a foreign key in the CurrentLocation's table. When you edit the CurrentLocationId without updating the CurrentLocation object as well, they become out of sync. This is what causes the exception in this case.
So let's say you needed to update the Person object's CurrentLocationId. We'll assume you pre-fetched the Person data and the Location data.
public class DbData
{
List<Person> PersonList;
List<Location> LocationList;
public DbData()
{
using (var context = new MyContext())
{
PersonList = context.Persons.ToList();
LocationList = context.Locations.ToList();
}
}
public void UpdatePersonLocation(Person person, int newLocationId)
{
using (var context = new MyContext())
{
var location = LocationList.Where(l=>l.id==newLocationId).Single();
//you need to update both the id and the location for this to not throw the exception
person.CurrentLocationId == newLocationId;
person.CurrentLocation == location;
context.Entry(person).State = System.Data.Entity.EntityState.Modified;
context.SaveChanges();
}
}
//or if you're giving it the location object...
public void UpdatePersonLocation(Person person, Location location)
{
using (var context = new MyContext())
{
//you need to update both the id and the location for this to not throw the exception
person.CurrentLocationId == location.id;
person.CurrentLocation == location;
context.Entry(person).State = System.Data.Entity.EntityState.Modified;
context.SaveChanges();
}
}
}
This might be an old post but the following worked for me
set the SaveOptions option to SaveOptions.DetectChangesBeforeSave

Categories