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


Критические изменения в EF Core 9 (EF9)

На этой странице описаны изменения API и поведения, которые могут нарушить обновление существующих приложений с EF Core 8 до EF Core 9. Проверьте предыдущие критические изменения при обновлении из более ранней версии EF Core:

Целевая платформа

EF Core 9 предназначен для .NET 8. Это означает, что существующие приложения, предназначенные для .NET 8, могут продолжать делать это. Приложения, предназначенные для более старых версий .NET, .NET Core и платформа .NET Framework, должны быть предназначены для .NET 8 или .NET 9 для использования EF Core 9.

Итоги

Примечание.

Если вы используете Azure Cosmos DB, ознакомьтесь с отдельным разделом ниже в Azure Cosmos DB с критическими изменениями.

Критические изменения Воздействие
исключение возникает при применении миграций при наличии ожидающих изменений модели Высокая
исключение возникает при применении миграций в явной транзакции Высокая
EF.Functions.Unhex() Теперь возвращается byte[]? Низкая
Проверено arity аргументов null в SqlFunctionExpression Низкая
ToString() Метод теперь возвращает пустую строку для null экземпляров Низкая
Зависимости общей платформы были обновлены до версии 9.0.x Низкая

Изменения высокой степени влияния

Исключение возникает при применении миграций при наличии ожидающих изменений модели

проблема отслеживания #33732

Старое поведение

Если модель имеет ожидающие изменения сравнительно с последней миграцией, они не применяются вместе с остальными миграциями при вызове Migrate.

Новое поведение

Начиная с EF Core 9.0, если модель имеет ожидающие изменения по сравнению с последней миграцией, исключение генерируется при вызове dotnet ef database update, Migrate или MigrateAsync:

Модель для контекста DbContext имеет ожидающие изменения. Добавьте новую миграцию перед обновлением базы данных. Это исключение можно отключить или зарегистрировать, передав идентификатор события "RelationalEventId.PendingModelChangesWarning" методу 'ConfigureWarnings' в 'DbContext.OnConfiguring' или 'AddDbContext'.

Почему

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

Устранение проблем

Существует несколько распространенных ситуаций, когда это исключение можно выбросить:

  • Миграции вообще нет. Это часто происходит при обновлении базы данных с помощью других средств.
    • устранения рисков. Если вы не планируете использовать миграции для управления схемой базы данных, удалите вызов Migrate или MigrateAsync, в противном случае добавьте миграцию.
  • Существует хотя бы одна миграция, но моментальный снимок модели отсутствует. Это обычно для миграций, созданных вручную.
    • Митигация: добавьте новую миграцию с использованием инструментария EF для обновления моментального снимка модели.
  • Модель не была изменена разработчиком, но она создана недетерминированным образом, что приводит к тому, что EF определяет её как изменённую. Это часто происходит, когда new DateTime(), DateTime.Now, DateTime.UtcNowили Guid.NewGuid() используются в объектах, предоставляемых HasData().
    • Уменьшение: добавьте новую миграцию, просмотрите ее содержимое, чтобы найти причину, и замените динамические данные статическим, жестко закодированным значением в модели. Миграция должна быть воссоздана после исправления модели. Если динамические данные должны использоваться для заполнения, рекомендуется использовать новый шаблон заполнения вместо HasData().
  • Последняя миграция была создана для другого поставщика, отличного от используемого для применения миграций.
  • Миграции создаются или выбираются динамически путем замены некоторых служб EF.
    • устранения рисков: предупреждение является ложным положительным в данном случае и должно быть отключено:

      options.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning))

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

Исключение возникает при применении миграций в явной транзакции

проблема отслеживания #17578

Старое поведение

Для применения миграций часто используется следующий шаблон:

await dbContext.Database.CreateExecutionStrategy().ExecuteAsync(async () =>
{
    await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
    await dbContext.Database.MigrateAsync(cancellationToken);
    await transaction.CommitAsync(cancellationToken);
});

Новое поведение

Начиная с EF Core 9.0, вызовы Migrate и MigrateAsync будут запускать транзакцию и выполнять команды с помощью ExecutionStrategy, а если приложение использует приведенный выше шаблон, создается исключение:

Ошибка возникла в предупреждении 'Microsoft.EntityFrameworkCore.Migrations.MigrationsUserTransactionWarning': транзакция была начата до применения миграций. Это предотвращает получение блокировки базы данных, поэтому база данных не будет защищена от параллельных приложений миграции. Транзакции и стратегия выполнения уже управляются EF как необходимо. Удалите внешнюю транзакцию. Это исключение можно подавить или зафиксировать, передав идентификатор события 'RelationalEventId.MigrationsUserTransactionWarning' методу 'ConfigureWarnings' в 'DbContext.OnConfiguring' или 'AddDbContext'.

Почему

Использование явной транзакции предотвращает получение блокировки базы данных, поэтому база данных не будет защищена от одновременных приложений миграции, она также ограничивает EF способом управления транзакциями внутри системы.

Устранение проблем

Если внутри транзакции есть только один вызов базы данных, удалите внешнюю транзакцию и ExecutionStrategy:

await dbContext.Database.MigrateAsync(cancellationToken);

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

options.ConfigureWarnings(w => w.Ignore(RelationalEventId.MigrationsUserTransactionWarning))

Изменения низкой степени влияния

EF.Functions.Unhex() Теперь возвращается byte[]?

Отслеживание проблемы #33864

Старое поведение

Функция EF.Functions.Unhex() была ранее аннотирована для возврата byte[].

Новое поведение

Начиная с EF Core 9.0, Unhex() теперь заметен для возврата byte[]?.

Почему

Unhex() преобразуется в функцию SQLite unhex , которая возвращает значение NULL для недопустимых входных данных. В результате Unhex() возвращено null для этих случаев в нарушение заметки.

Устранение проблем

Если вы уверены, что текстовое содержимое, переданное для Unhex() представления допустимой шестнадцатеричной строки, можно просто добавить оператор null-forgiving в качестве утверждения о том, что вызов никогда не вернет значение NULL:

var binaryData = await context.Blogs.Select(b => EF.Functions.Unhex(b.HexString)!).ToListAsync();

В противном случае добавьте проверку среды выполнения на значение NULL для возвращаемого значения Unhex().

Проверено arity аргументов null в SqlFunctionExpression

Отслеживание проблемы #33852

Старое поведение

Ранее можно было создать SqlFunctionExpression с другим числом аргументов и аргументами распространения значений NULL.

Новое поведение

Начиная с EF Core 9.0, EF теперь выдает, если число аргументов и аргументов распространения null не совпадает.

Почему

Не совпадающее число аргументов и аргументов распространения null может привести к непредвиденному поведению.

Устранение проблем

Убедитесь, что число argumentsPropagateNullability элементов совпадает с числом argumentsэлементов. Если сомнение используется false для аргумента NULL.

ToString() Метод теперь возвращает пустую строку для null экземпляров

Отслеживание проблемы #33941

Старое поведение

Ранее EF вернул несогласованные результаты для ToString() метода при значении nullаргумента. Например, ToString() для свойства с bool? возвращаемым nullзначением, но для выражений, не являющихся свойствамиnull, значение bool? которого было возвращеноnullTrue. Поведение также было несогласовано для других типов данных, например ToString() для null перечисления значений, возвращаемых пустой строкой.

Новое поведение

Начиная с EF Core 9.0 ToString() метод теперь последовательно возвращает пустую строку во всех случаях, когда значение аргумента равно null.

Почему

Старое поведение было несогласованным в разных типах данных и ситуациях, а также не совпадало с поведением C#.

Устранение проблем

Чтобы вернуться к старому поведению, переопределите запрос соответствующим образом:

var newBehavior = context.Entity.Select(x => x.NullableBool.ToString());
var oldBehavior = context.Entity.Select(x => x.NullableBool == null ? null : x.NullableBool.ToString());

Зависимости общей платформы были обновлены до версии 9.0.x

Старое поведение

Приложения, использующие Microsoft.NET.Sdk.Web пакет SDK и targetting net8.0, разрешают такие пакеты, как System.Text.Json, Microsoft.Extensions.Caching.MemoryMicrosoft.Extensions.Configuration.AbstractionsMicrosoft.Extensions.Loggingи Microsoft.Extensions.DependencyModel из общей платформы, поэтому эти сборки обычно не будут развертываться с приложением.

Новое поведение

Хотя EF Core 9.0 по-прежнему поддерживает net8.0, он теперь ссылается на версии System.Text.Json9.0.x , Microsoft.Extensions.Caching.MemoryMicrosoft.Extensions.Configuration.AbstractionsMicrosoft.Extensions.Loggingи .Microsoft.Extensions.DependencyModel Приложения, предназначенные для net8.0, не смогут использовать общую платформу, чтобы избежать развертывания этих сборок.

Почему

Соответствующие версии зависимостей содержат последние исправления безопасности и их использование упрощает модель обслуживания для EF Core.

Устранение проблем

Измените приложение на целевой net9.0, чтобы получить предыдущее поведение.

Критические изменения в Azure Cosmos DB

Обширная работа прошла в том, чтобы сделать поставщик Azure Cosmos DB лучше в 9.0. Изменения включают ряд критически важных изменений; Если вы обновляете существующее приложение, внимательно ознакомьтесь со следующими сведениями.

Критические изменения Воздействие
Дискриминатор теперь именуется $type вместо Discriminator Высокая
Свойство id больше не содержит дискриминационный по умолчанию Высокая
Свойство JSON id сопоставляется с ключом Высокая
Синхронизация операций ввода-вывода с помощью поставщика Azure Cosmos DB больше не поддерживается Средняя
Теперь запросы SQL должны проектируемые значения JSON напрямую Средняя
Неопределенные результаты теперь автоматически фильтруются из результатов запроса Средняя
Неправильно переведенные запросы больше не переводятся Средняя
HasIndex теперь бросает вместо того, чтобы игнорировать Низкая
IncludeRootDiscriminatorInJsonId был HasRootDiscriminatorInJsonId переименован в после версии 9.0.0-rc.2 Низкая

Изменения высокой степени влияния

Дискриминатор теперь именуется $type вместо Discriminator

Отслеживание проблемы #34269

Старое поведение

EF автоматически добавляет дискриминационные свойства в документы JSON, чтобы определить тип сущности, который представляет документ. В предыдущих версиях EF это свойство JSON используется Discriminator по умолчанию.

Новое поведение

Начиная с EF Core 9.0, дискриминатор теперь называется $type по умолчанию. Если у вас есть документы в Azure Cosmos DB из предыдущих версий EF, они используют старое Discriminator именование, а после обновления до EF 9.0 запросы к этим документам завершаются ошибкой.

Почему

Новая практика JSON использует $type свойство в сценариях, в которых необходимо определить тип документа. Например. System.Text.Json в NET также поддерживает полиморфизм, используя $type в качестве имени свойства по умолчанию (docs). Чтобы выровнять остальную часть экосистемы и упростить взаимодействие с внешними инструментами, было изменено значение по умолчанию.

Устранение проблем

Самый простой способ устранения рисков заключается в том, чтобы просто настроить имя дискриминационных свойств Discriminatorтак же, как и раньше:

modelBuilder.Entity<Session>().HasDiscriminator<string>("Discriminator");

Это делается для всех типов сущностей верхнего уровня, что делает EF так же, как и раньше.

На этом этапе вы также можете обновить все документы, чтобы использовать новое $type именование.

Теперь id свойство содержит только свойство ключа EF по умолчанию

Отслеживание проблемы #34179

Старое поведение

Ранее EF вставляет дискриминационные значения типа сущности в id свойство документа. Например, если вы сохранили тип сущности со свойством BlogId , содержащим 8, свойство JSON id будет содержать Blog|8.

Новое поведение

Начиная с EF Core 9.0 свойство JSON id больше не содержит дискриминационных значений и содержит только значение свойства ключа. В приведенном выше примере свойство JSON id будет просто 8. Если у вас есть документы в Azure Cosmos DB из предыдущих версий EF, они имеют дискриминационное значение в свойстве JSON id , а после обновления до EF 9.0 запросы к этим документам завершаются ошибкой.

Почему

Поскольку свойство JSON id должно быть уникальным, дискриминатор ранее был добавлен в него, чтобы разрешить различным сущностям с одинаковым значением ключа существовать. Например, это позволило иметь как a, Blog так и Post свойство со Id значением 8 в одном контейнере и секции. Это лучше соответствует шаблонам моделирования данных реляционной базы данных, где каждый тип сущности сопоставляется с собственной таблицей и поэтому имеет собственное пространство ключей.

EF 9.0 обычно изменил сопоставление, чтобы быть более согласованным с общими методиками и ожиданиями NoSQL Azure Cosmos DB, а не соответствовать ожиданиям пользователей, поступающих от реляционных баз данных. Кроме того, наличие дискриминационных значений в id свойстве затрудняет взаимодействие внешних средств и систем с документами JSON, созданными EF; такие внешние системы обычно не знают о дискриминационных значениях EF, которые по умолчанию являются производными от типов .NET.

Устранение проблем

Проще всего настроить EF для включения дискриминационных в свойство JSON id , как и раньше. Для этой цели был введен новый параметр конфигурации:

modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();

Это делается для всех типов сущностей верхнего уровня, что делает EF так же, как и раньше.

На этом этапе вы также можете обновить все документы, чтобы переписать их свойство JSON id . Обратите внимание, что это возможно только в том случае, если сущности разных типов не используют одно и то же значение идентификатора в одном контейнере.

Свойство JSON id сопоставляется с ключом

Отслеживание проблемы #34179

Старое поведение

Ранее EF создало теневое свойство, сопоставленное с свойством JSON id, если только одно из свойств не было сопоставлено с id явным образом.

Новое поведение

Начиная с EF Core 9, свойство ключа будет сопоставлено с свойством JSON id по соглашению, если это возможно. Это означает, что свойство ключа перестанет сохраняться в документе под другим именем, сохраняя то же значение, поэтому код, не использующий EF, который обрабатывает документы и полагается на наличие этого свойства, перестанет работать правильно.

Почему

EF 9.0 обычно изменил сопоставление, чтобы быть более согласованным с общими методиками и ожиданиями NoSQL в Azure Cosmos DB. И в документе не часто сохраняется значение ключа дважды.

Устранение проблем

Если вы хотите сохранить поведение EF Core 8, проще всего использовать новый вариант конфигурации, который был введен для этой цели:

modelBuilder.Entity<Session>().HasShadowId();

Это делается для всех типов сущностей верхнего уровня, что делает EF так же, как и раньше. Или можно применить его ко всем типам сущностей в модели с одним вызовом:

modelBuilder.HasShadowIds();

Изменения средней степени влияния

Синхронизация операций ввода-вывода с помощью поставщика Azure Cosmos DB больше не поддерживается

Отслеживание проблемы #32563

Старое поведение

Ранее вызов синхронных методов, таких ToList как или SaveChanges вызов EF Core, блокируют синхронно использование .GetAwaiter().GetResult() при выполнении асинхронных вызовов в пакете SDK Azure Cosmos DB. Это может привести к взаимоблокировке.

Новое поведение

Начиная с EF Core 9.0 EF теперь вызывается по умолчанию при попытке использовать синхронный ввод-вывод. Сообщение об исключении — "Azure Cosmos DB не поддерживает синхронный ввод-вывод. Не забудьте использовать и правильно ожидать только асинхронные методы при использовании Entity Framework Core для доступа к Azure Cosmos DB. Дополнительные сведения см. в статье https://aka.ms/ef-cosmos-nosync ".

Почему

Синхронная блокировка асинхронных методов может привести к взаимоблокировке, а пакет SDK Azure Cosmos DB поддерживает только асинхронные методы.

Устранение проблем

В EF Core 9.0 ошибка может быть отключена с помощью следующих способов:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.ConfigureWarnings(w => w.Ignore(CosmosEventId.SyncNotSupported));
}

Это говорится, что приложения должны перестать использовать API синхронизации с Azure Cosmos DB, так как это не поддерживается пакетом SDK Azure Cosmos DB. Возможность подавления исключения будет удалена в будущем выпуске EF Core, после чего единственным вариантом будет использование асинхронных API.

Теперь запросы SQL должны проектируемые значения JSON напрямую

Отслеживание проблемы 25527

Старое поведение

Ранее EF создал запросы, такие как следующие:

SELECT c["City"] FROM root c

Такие запросы приводят к тому, что Azure Cosmos DB упаковывает каждый результат в объект JSON следующим образом:

[
    {
        "City": "Berlin"
    },
    {
        "City": "México D.F."
    }
]
Новое поведение

Начиная с EF Core 9.0, EF теперь добавляет VALUE модификатор в запросы следующим образом:

SELECT VALUE c["City"] FROM root c

Такие запросы приводят к тому, что Azure Cosmos DB возвращает значения напрямую без упаковки:

[
    "Berlin",
    "México D.F."
]

Если приложение использует запросы SQL, такие запросы, скорее всего, будут нарушены после обновления до EF 9.0, так как они не включают VALUE модификатор.

Почему

Упаковка каждого результата в дополнительный объект JSON может привести к снижению производительности в некоторых сценариях, раздувает полезные данные результата JSON и не является естественным способом работы с Azure Cosmos DB.

Устранение проблем

Чтобы устранить эту проблему, просто добавьте VALUE модификатор в проекции запросов SQL, как показано выше.

Неопределенные результаты теперь автоматически фильтруются из результатов запроса

Отслеживание проблемы 25527

Старое поведение

Ранее EF создал запросы, такие как следующие:

SELECT c["City"] FROM root c

Такие запросы приводят к тому, что Azure Cosmos DB упаковывает каждый результат в объект JSON следующим образом:

[
    {
        "City": "Berlin"
    },
    {
        "City": "México D.F."
    }
]

Если какой-либо из результатов не определен (например City , свойство отсутствует в документе), возвращается пустой документ, и EF возвращается null для этого результата.

Новое поведение

Начиная с EF Core 9.0, EF теперь добавляет VALUE модификатор в запросы следующим образом:

SELECT VALUE c["City"] FROM root c

Такие запросы приводят к тому, что Azure Cosmos DB возвращает значения напрямую без упаковки:

[
    "Berlin",
    "México D.F."
]

Поведение Azure Cosmos DB заключается в автоматическом фильтрации undefined значений из результатов. Это означает, что если один из City свойств отсутствует в документе, запрос вернет только один результат, а не два результата, с одним из nullних.

Почему

Упаковка каждого результата в дополнительный объект JSON может привести к снижению производительности в некоторых сценариях, раздувает полезные данные результата JSON и не является естественным способом работы с Azure Cosmos DB.

Устранение проблем

Если получение null значений для неопределенных результатов важно для приложения, выполните undefined объединение значений с null использованием нового EF.Functions.Coalesce оператора:

var users = await context.Customer
    .Select(c => EF.Functions.CoalesceUndefined(c.City, null))
    .ToListAsync();

Неправильно переведенные запросы больше не переводятся

Отслеживание проблемы #34123

Старое поведение

Ранее ef переведенные запросы, такие как следующие:

var sessions = await context.Sessions
    .Take(5)
    .Where(s => s.Name.StartsWith("f"))
    .ToListAsync();

Однако преобразование SQL для этого запроса было неверным:

SELECT c
FROM root c
WHERE ((c["Discriminator"] = "Session") AND STARTSWITH(c["Name"], "f"))
OFFSET 0 LIMIT @__p_0

В SQL WHERE предложение вычисляется предложением и OFFSET предложениями, LIMIT но в приведенном выше запросе LINQ оператор отображается перед операторомTake. В результате такие запросы могут возвращать неверные результаты.

Новое поведение

Начиная с EF Core 9.0 такие запросы больше не переводятся, и создается исключение.

Почему

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

Устранение проблем

Если вы были довольны предыдущим поведением и хотели бы выполнить тот же SQL, просто переключите порядок операторов LINQ:

var sessions = await context.Sessions
    .Where(s => s.Name.StartsWith("f"))
    .Take(5)
    .ToListAsync();

К сожалению, Azure Cosmos DB в настоящее время не поддерживает OFFSET предложения в LIMIT вложенных запросах SQL, что является правильным переводом исходного запроса LINQ.

Изменения низкой степени влияния

HasIndex теперь бросает вместо того, чтобы игнорировать

Отслеживание проблемы #34023

Старое поведение

Ранее вызовы HasIndex были проигнорированы поставщиком EF Cosmos DB.

Новое поведение

Теперь поставщик создает исключение, если HasIndex задано.

Почему

В Azure Cosmos DB все свойства индексируются по умолчанию, и индексирование не требуется указывать. Хотя можно определить настраиваемую политику индексирования, она в настоящее время не поддерживается EF и может выполняться через портал Azure без поддержки EF. Так как HasIndex звонки ничего не делают, они больше не допускаются.

Устранение проблем

Удалите все вызовы HasIndex.

IncludeRootDiscriminatorInJsonId был HasRootDiscriminatorInJsonId переименован в после версии 9.0.0-rc.2

Отслеживание проблемы #34717

Старое поведение

API IncludeRootDiscriminatorInJsonId появился в версии 9.0.0 rc.1.

Новое поведение

Для окончательного выпуска EF Core 9.0 API был переименован в HasRootDiscriminatorInJsonId

Почему

Другой связанный API был переименован, чтобы начать Has с, а не Include, поэтому он был переименован для согласованности.

Устранение проблем

Если код использует IncludeRootDiscriminatorInJsonId API, просто измените его на ссылку HasRootDiscriminatorInJsonId .