Поделиться через


Сохранение данных

При выполнении запросов можно считывать данные из базы данных, сохранять данные означает добавление новых сущностей в базу данных, удаление сущностей или изменение свойств существующих сущностей в некотором смысле. Entity Framework Core (EF Core) поддерживает два основных подхода для сохранения данных в базе данных.

Подход 1. Отслеживание изменений и SaveChanges

Во многих сценариях программа должна запрашивать некоторые данные из базы данных, выполнять некоторые изменения и сохранять эти изменения обратно; иногда это называется "единицей работы". Например, предположим, что у вас есть набор блогов, и вы хотите изменить Url свойство одного из них. В EF обычно это делается следующим образом:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Single(b => b.Url == "http://example.com");
    blog.Url = "http://example.com/blog";
    context.SaveChanges();
}

Приведенный выше код выполняет следующие действия.

  1. Он использует обычный запрос LINQ для загрузки сущности из базы данных (см. сведения о запросах). Запросы EF отслеживаются по умолчанию, что означает, что EF отслеживает загруженные сущности во внутреннем отслеживании изменений.
  2. Загруженный экземпляр сущности управляется обычным путем назначения свойства .NET. EF не участвует в этом шаге.
  3. Наконец, DbContext.SaveChanges() вызывается. На этом этапе EF автоматически обнаруживает любые изменения, сравнивая сущности с моментальным снимком с момента их загрузки. Все обнаруженные изменения сохраняются в базе данных; при использовании реляционной базы данных обычно это предполагает отправку, например SQL UPDATE для обновления соответствующих строк.

Обратите внимание, что описанная выше типичная операция обновления для существующих данных, но аналогичные принципы хранятся для добавления и удаления сущностей. Вы взаимодействуете с средство отслеживания изменений EF путем вызова DbSet<TEntity>.Add и Remove, что приводит к отслеживанию изменений. Затем EF применяет все отслеживаемые изменения к базе данных при SaveChanges() вызове (например, через SQL INSERT и DELETE при использовании реляционной базы данных).

SaveChanges() предлагает следующие преимущества:

  • Вам не нужно писать код для отслеживания измененных сущностей и свойств . EF делает это автоматически и обновляет только эти свойства в базе данных, повышая производительность. Представьте, что загруженные сущности привязаны к компоненту пользовательского интерфейса, что позволяет пользователям изменять любое свойство, которое они хотят; EF отнимает бремя выяснения того, какие сущности и свойства были на самом деле изменены.
  • Сохранение изменений в базе данных иногда может быть сложным! Например, если вы хотите добавить блог и некоторые записи для этого блога, может потребоваться получить ключ, созданный базой данных для вставленного блога, прежде чем можно вставить записи (так как они должны ссылаться на блог). EF делает все это для вас, убирая сложность.
  • EF может обнаруживать проблемы с параллелизмом, например, когда строка базы данных была изменена другим пользователем между запросом и SaveChanges(). Дополнительные сведения доступны в конфликтах параллелизма.
  • В базах данных, поддерживающих ее, SaveChanges() автоматически упаковывает несколько изменений в транзакцию, обеспечивая согласованность данных в случае сбоя. Дополнительные сведения доступны в транзакциях.
  • SaveChanges() также пакетирует несколько изменений во многих случаях, значительно уменьшая количество циклов баз данных и значительно повышая производительность. Дополнительные сведения доступны в эффективном обновлении.

Дополнительные сведения и примеры кода по базовому SaveChanges() использованию см. в разделе Basic SaveChanges. Дополнительные сведения об отслеживании изменений EF см. в обзоре отслеживания изменений.

Подход 2. ExecuteUpdate и ExecuteDelete ("массовое обновление")

Примечание.

Эта функция появилась в EF Core 7.0.

Хотя отслеживание изменений и SaveChanges() эффективный способ сохранения изменений, они имеют определенные недостатки.

Во-первых, необходимо запросить и отслеживать все сущности, SaveChanges() которые будут изменяться или удаляться. Если вам нужно, скажем, удалить все блоги с рейтингом ниже определенного порогового значения, необходимо запрашивать, материализовать и отслеживать потенциально огромное количество строк и SaveChanges() создать DELETE инструкцию для каждого из них. Реляционные базы данных предоставляют гораздо более эффективную альтернативу: можно отправить одну DELETE команду, указав, какие строки следует удалить с помощью WHERE предложения, но SaveChanges() модель не позволяет создать это.

Для поддержки этого сценария массового обновления можно использовать ExecuteDelete следующее:

context.Blogs.Where(b => b.Rating < 3).ExecuteDelete();

Это позволяет выразить инструкцию SQL DELETE с помощью обычных операторов LINQ, аналогичных обычному запросу LINQ, что приводит к выполнению следующего SQL в базе данных:

DELETE FROM [b]
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Это выполняется очень эффективно в базе данных без загрузки данных из базы данных или отслеживания изменений EF. Аналогичным образом ExecuteUpdate можно выразить инструкцию SQL UPDATE .

Даже если вы не изменяете сущности массово, вы можете точно знать, какие свойства какой сущности вы хотите изменить. Использование API отслеживания изменений для выполнения изменения может быть чрезмерно сложным, требуя создания экземпляра сущности, отслеживания его с помощью Attachвнесения изменений и, наконец, вызова SaveChanges(). Для таких ExecuteUpdate сценариев и ExecuteDelete может быть значительно проще выразить ту же операцию.

Наконец, отслеживание изменений и SaveChanges() само по себе накладывает определенные затраты на среду выполнения. Если вы пишете высокопроизводительное приложение и ExecuteUpdate ExecuteDelete позволяет избежать обоих этих компонентов и эффективно создавать нужную инструкцию.

Однако обратите внимание, что ExecuteUpdate у ExecuteDelete вас есть некоторые ограничения:

  • Эти методы выполняются немедленно и в настоящее время нельзя пакетировать с другими операциями. С другой стороны, SaveChanges()можно выполнять пакетную обработку нескольких операций вместе.
  • Так как отслеживание изменений не участвует, это ваша ответственность точно знать, какие сущности и свойства необходимо изменить. Это может означать больше ручного, низкоуровневого отслеживания кода, что необходимо изменить и что не делает.
  • Кроме того, так как отслеживание изменений не участвует, эти методы не применяются автоматически при сохранении изменений. Однако вы по-прежнему можете явно добавить предложение для реализации управления параллелизмом Where самостоятельно.
  • В настоящее время поддерживается только обновление и удаление; Необходимо выполнить вставку с помощью DbSet<TEntity>.Add и SaveChanges().

Дополнительные сведения и примеры кода см. в разделе ExecuteUpdate и ExecuteDelete.

Итоги

Ниже приведены некоторые рекомендации по использованию какого подхода. Обратите внимание, что это не абсолютные правила, но укажите полезные правила отпечатка:

  • Если вы заранее не знаете, какие изменения будут происходить, используйте SaveChangesего; он автоматически обнаружит, какие изменения необходимо применить. Примеры сценариев:
    • "Я хочу загрузить блог из базы данных и отобразить форму, позволяющую пользователю изменить его"
  • Если вам нужно управлять графом объектов (т. е. несколькими взаимосвязанными объектами), используйте SaveChanges; он определит правильное упорядочивание изменений и как связать все вместе.
    • "Я хочу обновить блог, изменить некоторые из своих записей и удалить другие"
  • Если вы хотите изменить потенциально большое количество сущностей на основе некоторых критериев, используйте ExecuteUpdate и ExecuteDelete. Примеры сценариев:
    • "Я хочу дать всем сотрудникам повышение"
    • "Я хочу удалить все блоги, имя которого начинается с X"
  • Если вы уже знаете, какие сущности вы хотите изменить и как их изменить, использовать ExecuteUpdate и ExecuteDelete. Примеры сценариев:
    • "Я хочу удалить блог, имя которого — Foo"
    • "Я хочу изменить имя блога с идентификатором 5 на "Bar"