EntityFrameworkCore вставляет существующие связанные элементы

Рассматривать

 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;}
 }

Теперь я использую ядро ​​Entity Framework для добавления ItemTag в Item. Это работает просто отлично.

Теперь я добавляю второй ItemTag к тому же Item. При сохранении передается весь объект, включая существующие связанные ItemTags. Затем EF пытается вставить существующий ItemTag, который завершается ошибкой, за исключением "Невозможно вставить значение в столбец идентификаторов..."

Итак, как я могу предотвратить вставку существующего объекта?

Мой обходной путь состоит в том, чтобы перебрать ItemTags и установить любой, у которого есть Id, в EntityState. Без изменений, чтобы заставить его не сохранять его. Но, похоже, такой обходной путь не требуется.

Это код, который сохраняет элемент:

//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();

3 ответа

AutoMapper не подходит для возврата к постоянным или доменным моделям. Автор AutoMapper заявил.

Это также не очень хорошо работает, когда EF (Core) отслеживает изменения.

Отслеживание изменений выполняется на основе сущностей, загруженных из базы данных. Когда вы меняете значение сущности, его состояние меняется с Unchanged в Modified и приведет к методу обновления.

Когда вы загружаете сущность без отношения предметов (используя готовую загрузку), то при добавлении сущности она будет помечена как Added, в результате чего вставка при сохранении.

У вас также есть возможность Attach а также Detach юридические лица.

Из связанного сообщения в блоге:

Роуэн Миллер подытожила новое поведение в проблеме GitHub (bit.ly/295goxw):

Добавить: добавляет каждую достижимую сущность, которая еще не отслежена.

Присоединить: присоединяет каждую достижимую сущность, кроме случаев, когда достижимая сущность имеет сгенерированный хранилищем ключ и значение ключа не назначается; они будут помечены как добавленные.

Обновление: то же, что и "Присоединить", но объекты помечены как измененные.

Удалить: То же, что и "Присоединить", а затем пометить корень как удаленный. Поскольку каскадное удаление теперь происходит в SaveChanges, это позволяет каскадным правилам передаваться сущностям позже.

Таким образом, вам придется прикрепить свои элементы (если вы хотите добавить новые без первичного ключа) или позвонить Update/UpdateRange на DbSet обновить те с ключом и добавить те без.

Или обработайте его самостоятельно, установив / изменив его отслеживаемое состояние, как вы уже сделали. Но имейте в виду, Unchanged не будет обновлять пропущенные объекты.

Для вставки нового ItemTagВы можете вставить в ItemTag таблица непосредственно, не отражая Item,

Определите вашу модель, как показано ниже:

public class ItemTag
{
    public int Id { get; set; }
    public int Name { get; set; }
    public int ItemId { get; set; }
    public virtual Item Item { get; set; }
}

Попробуйте установить ItemId за ItemTag вставить новый ItemTag за Item,

Если вы предпочитаете вставить Item с ItemTagможно попробовать JsonPatchDocument которые описывают, как управлять моделью.

Попробуйте сослаться на JSON Patch With ASP.net Core.

За JsonPatchDocument, вам нужно сгенерировать JsonPatchDocument<Item> на стороне клиента. попытаться сослаться на ASP Core PatchDocument, возвращающий неверный ввод.

Это поведение связано с отслеживанием объектов.

Когда у вас есть отношение, где элемент A содержит коллекцию элементов B, как

 public class A 
 {
    List<B> ItemsB {get; set;}
 }

и вы хотите обновить некоторые элементы B, вам нужно выбрать A, изменить коллекцию B на основе входных данных и затем сохранить изменения в контексте. Что-то вроде того:

public async Task UpdateItem(A inputA)
{
  var itemA = await this.DbContext.Items.SingleAsync(a => a.Id == inputA.Id);
  foreach(inputB in inputA.ItemsB)
  {
      var indexB = itemA.ItemsB.IndexOf(b => b.Id == inputB.Id);
      if(indexB < 0) 
      {
         itemA.ItemsB.Add(inputB);
      }
      else 
      {
         itemsA.ItemsB[indexB].Property1 = inputB.Property1; // you should not lost tracking with that behavior
      }
  }
  await this.DbContext.SaveChangesAsync();
}

И вам нужно помнить, чтобы не заменять существующий ItemB в ItemA, потому что EF будет рассматривать его как новый объект с тем же Id, который уже существует в базе данных, просто сопоставьте свойства вручную.

PS. этот код не распространяется на удаление элементов из БД.