Предпочтительный сценарий для обновления один ко многим (как один, так и многие должны быть обновлены)

#c# #entity-framework #asp.net-core #entity-framework-core

#c# #entity-framework #asp.net-ядро #сущность-структура-ядро

Вопрос:

Каков рекомендуемый способ обновления как книги, так и страниц в этом сценарии «один ко многим» (ядро EF)?

Книга содержит одну или несколько страниц. Каждая страница принадлежит только одной книге, и страницы не существуют сами по себе.

Сущности:

 class Book
{
    public int Id {get; set;}
    public string Title {get; set;}
    public string ISBN {get; set;}
    
    public virtual ICollection<Page> Pages {get; set;}
}

class Page
{
    public int Id { get; set; }
    public int PageNumber { get; set; }
    public string Text { get; set; }

    public int BookId { get; set; }
    public virtual Book Book { get; set; }
}
 

способ обновления существующей книги:

 /// <summary>
/// Save an existing book including pages
/// </summary>
public void SaveExistingBook(Book bookToUpdate)
{
    var bookEntity = _databaseContext.Books.Including(b => b.Pages).SingleOrDefault(b => b.Id == bookToUpdate.Id);
    ... etc....
}
 

Параметр bookToUpdate содержит все свойства книги и страницы с их свойствами, какими они должны стать.
(Возможно, изменились свойства книги (название, ISBN), а также свойства на страницах (изменения текста).).
Кроме того, страницы могли быть добавлены или удалены.)

Каков правильный способ реализации такого метода?

  1. Удалить исходную книгу со страницами и вставить новую книгу и страницы?
  2. Выполните итерацию по всем страницам и удалите / вставьте удаленные / новые страницы и обновите свойства на измененных страницах. Обновите свойства книги.
  3. Другие предложения?

Спасибо!

Ответ №1:

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

Лучший подход — использовать стороннюю библиотеку, такую как Entity Framework Plus, она предлагает пакетное обновление и пакетное удаление.

Итак, для вашего случая все, что вам нужно сделать, это создать выражение условного обновления и вызвать метод пакетного обновления:

 _databaseContext.Books.Where(b => b.Id == bookToUpdate.Id)
                      .Update(b => /* your update expression */);
 

Ответ №2:

Вот мой предпочтительный способ обновления отношения «один ко многим»:

 public void SaveExistingBook(Book bookToUpdate)
{
    var bookInDb = _databaseContext.Books.Including(b => b.Pages).FirstOrDefault(b => b.Id == bookToUpdate.Id);
    if (bookInDb == null) return;

    // First update book properties
    bookInDb.Name = bookToUpdate.Name;
    //...

    // Delete all removed pages
    var pagesToDelete = (from page in bookInDb.Pages
                         let item = bookToUpdate.Pages.SingleOrDefault(p => p.Id == page.Id)
                         where item == null
                         select page).ToList();

    if (pagesToDelete.Any())
        foreach (var page in pagesToDelete)
            _databaseContext.Entry(page).State = EntityState.Deleted;

    // Add all new pages
    foreach (var page in bookInDb.Pages.Where(p => p.Id < 1).ToList())
    {
        page.BookId = bookInDb.Id;
        _databaseContext.Pages.Add(page);
    }

    // Modify existing pages
    foreach (var pageToUpdate in bookInDb.Pages.Where(p => p.Id > 0 amp;amp; pagesToDelete.All(pg.Id == p.Id)).ToList())
    {
        var updatedPage = bookToUpdate.Pages.First(p => p.Id == pageToUpdate.Id);
        db.Entry(pageToUpdate).CurrentValues.SetValues(updatedPage);
    }
}
 

Вы также можете использовать DbContext.Update метод. этот метод приводит к тому, что объект отслеживается контекстом как измененный. Еще раз, контекст не имеет никакого способа определить, какие значения свойств были изменены, и будет генерировать SQL для обновления всех свойств. Где этот метод отличается от явного задания свойства состояния.