Reputation: 21
I'm trying to save only changed entities.
If I remove this if
:
if (!period.IsSame(_context.Periods.First(p => p.ID == period.ID)))
everything is fine.
But if I keep it, on the statement _context.Attach(period);
or same if I use Update
, I get an error:
InvalidOperationException: The instance of entity type 'Period' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked.
I don't know how test that it's really modified.
public async Task<IActionResult> OnPostAsync(List<Period> periods)
{
if (!ModelState.IsValid)
{
return Page();
}
// _context.Periods.Add(Period);
int i = 0;
foreach (var period in periods)
{
TimeOnly startTime = TimeOnly.Parse(Request.Form["StartTime" + i].ToString());
TimeOnly endTime = TimeOnly.Parse(Request.Form["EndTime" + i].ToString());
period.StartHour = startTime.Hour;
period.StartMinute = startTime.Minute;
period.EndHour = endTime.Hour;
period.EndMinute = endTime.Minute;
period.StatusDate = DateTime.Now;
// if it already exists
if (period.ID > 0)
{
// if modified
if (!period.IsSame(_context.Periods.First(p => p.ID == period.ID)))
{
_context.Attach(period);
if (period.Delete)
{
period.Status = (int)Status.deleted;
}
else
{
period.Status = (int)Status.modified;
}
}
}
// if new
else
{
period.Status = (int)Status.created;
_context.Attach(period);
}
i++;
}
await _context.SaveChangesAsync();
return RedirectToPage("./Periods");
}
I have tried both update and attach. I have search for entity tracking but it seems to be detached as soon as it's on a webpage
Upvotes: 1
Views: 54
Reputation: 142628
EF uses concept of change tracking to determine what should be done with entities. By default querying data will lead to context starting to track it hence the exception. You can mitigate it by disabling tracking by default, for example using .AsNoTracking()
:
if (!period.IsSame(_context.Periods.AsNoTracking().First(p => p.ID == period.ID)))
{
// ...
}
But this is not very advisable approach, due to multiple reasons - possibly change tracker will not detect any changes and you will need to handle that manually, and bigger reason - you will be querying database in a loop which is bad for application performance. Just fetch everything from the database and update it accordingly:
var existingPeriods = await _context.Periods
.Where(p => periods.Select(p => p.ID).Contains(p.ID))
.ToListAsync(); // or ToDictionaryAsync if there a lot of the periods
foreach (var period in periods)
{
var existing = existingPeriods.FirstOrDefault(p => p.ID == period.ID);
if (existing != null)
{
// maybe throw if period.ID != 0
// update data in existing
existing. ... = ...;
}
else
{
// if new ...
_context.Periods.Add(period);
}
await _context.SaveChangesAsync();
}
Upvotes: 1