Consider
public class Item
{
public int Id{get;set;}
public string Name{get;set;}
public List<ItemTag> ItemTags{get;set;}
}
public class ItemTag
{
public int Id{get;set;}
public int Name{get;set;}
}
I now use entity framework core to add an ItemTag to Item. This works just fine.
Now I add a second ItemTag to the same Item. When saving, the entire object is passed, including the existing related ItemTags. EF then tries to insert the existing ItemTag which fails with an exception of 'Cannot Insert a value into an Identity Column...'
So how do I prevent the existing object from getting Inserted?
My workaround is to loop through the ItemTags, and set any that have an Id to EntityState.Unchanged to force it not to save it. But it seems that such a workaround should not be required.
This is the code that save the item:
//Get the current item, so that only updated fields are saved to the database.
var item = await this.DbContext.Items.SingleAsync(a => a.Id == itemInput.Id);
item.UpdatedBy = this._applicationUserProvider.CurrentAppUserId;
item.Updated = DateTimeOffset.UtcNow;
//Use Automapper to map fields.
this._mapper.Map(itemInput, item);
//Workaround for issue.
foreach (var itemTag in item.ItemTags)
{
var entry = this.DbContext.Entry(itemTag);
if (itemTag.Id > 0)
{
entry.State = EntityState.Unchanged;
}
}
await this.SaveChangesAsync();
AutoMapper isn't suitable for backing back to persistence or domain models. The author of AutoMapper stated.
This also doesn't work well in the way EF (Core) does change tracking.
Change tracking is done, based on the entities that are loaded form the database. When you change an entities value its state changes from
Unchanged
toModified
and will result in an update method.When you load an entity w/o the items relation (using eager loading), then when you add an entity it will me marked as
Added
, resulting in an insert when saved.You also have the possibility to
Attach
andDetach
entities.From the linked blog post:
So you'll have to attach your items (if you only want to add new ones w/o a primary key) or call
Update/UpdateRange
on theDbSet
to update the ones with a key and add the ones without.Alternatively handle it yourself by setting/changing its tracked state as you already did. But keep in mind,
Unchanged
won't update the passed entities.