-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EF Core AddRange and entities with duplicate keys #24780
Comments
Normally it is common to do identity resolution before tracking and saving. For example: foreach (var item in items)
{
var existing = context.AList.Local.FirstOrDefault(e => e.Id == item.Id);
if (existing != null)
{
existing.A = item.A;
}
else
{
context.Add(item);
}
}
context.SaveChanges(); However, this will only save the last value. If you want to save the first value and then replace it with subsequent values, then this will require calling SaveChanges more than once. For example: foreach (var item in items)
{
var existing = context.AList.Local.FirstOrDefault(e => e.Id == item.Id);
if (existing != null)
{
context.SaveChanges();
existing.A = item.A;
}
else
{
context.Add(item);
}
}
context.SaveChanges(); |
Thanks for the feedback. Does this imply the only mechanisms EF has to accomplish my goal are:
UPDATE Items SET A = 'Foo' WHERE Id = 1
UPDATE Items SET A = 'Bar' WHERE Id = 1 I hesitate to call |
@epatrick you don't need to call SaveChanges on a per-item basis - note that it's outside of the loop in the above samples. You're indeed correct that batching multiple updates in a single SaveChanges can be important for performance. |
@roji You do if you want the same item to be updated more than once in the database. @epatrick EF Core does batch updates, but it is limited to a single update per entity instance per call to SaveChanges. The way I wrote the code above it batches as much as it can before saving, but it must save before another update to the same instance can be made. |
My bad, missed the bit where two database updates are desired for the same row. |
You might want to consider explicitly beginning a transaction over the multiple SaveChanges if you want an error during the process to rollback the saves done up to that point |
Excellent feedback folks; thank you! @stevendarby, agreed on the transaction. @ajcvickers, sorry I missed that My next challenge would be to abstract matching Lastly, can I abstract |
@epatrick For setting values: context.Entry(existing).CurrentValues.SetValues(item); #7391 is tracking making working with key values easier. For now, it requires some code. For example: var keyPropertyNames
= context.Model.FindEntityType(typeof(ItemA)).FindPrimaryKey().Properties.Select(e => e.Name);
foreach (var item in items)
{
var existing = context.ChangeTracker
.Entries<ItemA>()
.FirstOrDefault(e => AreSameEntity(e, context.Entry(item)));
if (existing != null)
{
context.SaveChanges();
existing.CurrentValues.SetValues(item);
}
else
{
context.Add(item);
}
}
context.SaveChanges();
bool AreSameEntity(EntityEntry left, EntityEntry right)
{
foreach (var propertyName in keyPropertyNames)
{
if (!Equals(left.Property(propertyName).CurrentValue, right.Property(propertyName).CurrentValue))
{
return false;
}
}
return true;
} |
Awesome - thanks for the help! |
I think SetValues sets all matching properties and may not meet your “non-null properties” requirement, so some reflection might be required (assuming you’re aiming to code once for multiple types and not just |
I have a use case where a third party provides an enumeration of items that I wish to merge into a database using EF Core. There are use cases where the third party provides an item with the same key more than once in the enumeration.
Ideally, I would like BOTH updates to 12345 to take place (we audit history in the data tier).
I get an error when attempting to add 12345 twice to the same context. Code POC is:
yields:
What's the appropriate way to manage this use case?
The text was updated successfully, but these errors were encountered: