Note that this is NOT Bulk Update, every record will have its own unique update value.
I have a user-ordered list of items (Id
, Name
, Position
) where user can freely reorder the items. Now when user submits a new ordered list, I would like to update all of their Position
s. I have a list of Id
s matching with their Position
s (note that I do not have any other information like Name
and do not want to lose it).
How do I make EF Core to submit a single round trip with many UPDATE
statement without having to fetch them first?
Below are the approaches and their limitations:
ctx.Products
.Where(q => q.Id == item.Id)
.ExecuteUpdate(q => q.SetProperty(p => p.Position, _ => item.Position))
var idList = new int[] { 1, 2, 3, 4 };
using (var db = new SomeDatabaseContext())
{
var friends = db.Friends.Where(f => idList.Contains(f.ID)).ToList();
friends.ForEach(a => a.msgSentBy = '1234');
db.SaveChanges();
}
Name
becomes ""): var posList = new (int, int)[]
{
(1, 2),
(2, 1),
(3, 1),
};
var ctx = new TestContext();
foreach (var (id, pos) in posList)
{
var entity = new DbItem
{
Id = id,
Name = "", // Required property
Position = pos,
};
var entry = ctx.Attach(entity);
entry.State = EntityState.Modified;
entry.Property(q => q.Position).IsModified = true;
}
await ctx.SaveChangesAsync();
Attach
instead, however it results in exception:// The surrounding code is similar to the above code
var entity = new DbItem
{
Id = id,
Name = "", // Required property
Position = -1, // To make sure it will be changed
};
var entry = ctx.Attach(entity);
entity.Position = pos;
Unhandled exception. Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
I am using EF Core 7.
If you are sure that the data is present in database (or ok in case of failure for missing ones) you can try leveraging the ability to attach entities and set state:
List<Products> entities = ...
var ctx = ...;
ctx.AttachRange(entities);
foreach (var ent in entities)
{
ctx.Entry(ent).State = EntityState.Modified;
}
ctx.SaveChanges();
My approach by modifying the Change Tracker manually results in data loss
Do not set state, update the value:
foreach (var (id, pos) in posList)
{
var entity = new DbItem
{
Id = id,
Position = pos - 1
};
var entry = ctx.Attach(entity);
entity.Position = pos;
}