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


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

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

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

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

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

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

  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 ("массовое обновление")

Хотя отслеживание изменений и 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"