"Каскадные" вставки EF Core 2.0 для связанных объектов при обновлении основного объекта
Веб-приложение ASP.NET Core 2, использующее REST API. В настоящее время используется sqlite3 для разработки базы данных. (Также попытался перейти на SQL Server и получил те же результаты, что и ниже).
Я отправляю сущность веб-клиенту, клиент вносит изменения в сущность, которые включают добавление новой связанной сущности, а затем эта обновленная основная сущность отправляется обратно в виде json в теле запроса PUT.
Я надеялся, что новая связанная сущность будет создана автоматически, но этого не происходит. Простые свойства основного объекта обновляются должным образом, но не ссылочные свойства. Я не получаю никаких исключений или чего-то еще - просто кажется, что игнорируются ссылочные свойства.
Упрощенные классы (я удалил другие свойства, которые не должны влиять на отношения):
public partial class DashboardItem {
public int Id { get; set; }
public int? DataObjectId { get; set; }
public DataObject DataObject { get; set; }
}
public partial class DataObject {
public int Id { get; set; }
}
Часть DbContext Fluent API для связанного свойства:
modelBuilder.Entity<DashboardItem>(entity => {
entity.HasOne(p => p.DataObject)
.WithMany()
.HasForeignKey(p => p.DataObjectId);
});
Метод контроллера для PUT:
[HttpPut("{id}")]
public async Task<IActionResult> PutDashboardItem([FromRoute] int id, [FromBody] DashboardItem entity)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != entity.Id)
{
return BadRequest();
}
_context.Entry(entity).State = EntityState.Modified;
try{
await _context.SaveChangesAsync();
}catch (DbUpdateConcurrencyException)
{
if (!DashboardItemExists(id)){
return NotFound();
}else {
throw;
}
}
return NoContent();
}
Упрощенный json (без всех других свойств) будет выглядеть следующим образом (я пробовал разные варианты удаления внешнего ключа "DataObjectId" из json, установки в ноль или в ноль на случай, если это может помешать.):
{
Id:1,
DataObjectId:null,
DataObject:{
Id: 0
}
}
При отладке в методе действия контроллера существующий объект принципа "DashboardItem", созданный из тела запроса, имеет свойство ссылки "DataObject", заполняемое перед добавлением в DbContext, но новый DataObject никогда не создается в базе данных. Существует только инструкция SQL UPDATE для DashboardItem и нет INSERT для DataObject.
Я также попытался сделать метод контроллера синхронным вместо асинхронного, используя DbContext.SaveChanges() вместо.SaveChangesAsync(), поскольку в более ранних версиях EF Core возникала проблема, связанная с созданием связанных сущностей, даже если Я использую 2.0, который уже имеет исправление для этого. Тот же результат.
Этот документ EFCore Doc звучит так, как будто он должен работать из коробки.
Это сработало для меня в предыдущем проекте. Что мне здесь не хватает?
1 ответ
По сути, моя ошибка заключалась в том, что процесс обновления данных был намного проще, чем при отправке обновленных данных от клиента в веб-приложении.
После того, как я покопался намного больше, кажется, что следующая строка в моем методе контроллера для обработки запроса PUT является проблемой:
_context.Entry(entity).State = EntityState.Modified;
Установка состояния входа объекта в значение Modified таким образом приводит к тому, что Entity Framework Core игнорирует ссылочные свойства для связанных объектов - сгенерированный SQL UPDATE будет адресован только столбцам в таблице объектов.
Это простое резюме в конечном итоге привело меня к правильному пути.
Подводя итог, что я теперь узнал:
Этот метод контроллера имеет дело с "отделенным" объектом, который был отредактирован и отослан обратно клиенту. DbContext еще не отслеживает эту сущность, так как я получаю новый экземпляр контекста с каждым запросом http (следовательно, сущность считается отсоединенной). Поскольку он еще не отслеживается, когда он добавляется в DbContext, необходимо сообщить контексту, был ли изменен этот объект и как его обрабатывать.
Есть несколько способов сообщить DbContext, как обрабатывать отдельную сущность. Среди тех:
(1) установка состояния объекта в EntityState. Модификация приведет к тому, что ВСЕ свойства будут включены в обновление SQL (независимо от того, изменились они на самом деле или нет), ЗА ИСКЛЮЧЕНИЕМ для ссылочных свойств для связанных объектов:
_context.Entry(entity).State = EntityState.Modified;
(2) добавление объекта с вызовом DbContext.Update будет делать то же, что и выше, но будет включать свойства ссылки, а также включать ВСЕ свойства этих объектов в обновлении, независимо от того, изменились они или нет:
_context.Update(entity)
Подход №2 заставил меня работать, и я просто пытался создать новую связанную дочернюю сущность в Обновлении ее родителя.
Кроме того, DbContext.Attach() и DbContext.TrackGraph звучат как ваш, обеспечивают более детальный контроль над указанием, какие конкретные свойства или связанные объекты должны быть включены в обновление.