Критические изменения в EF Core 9 (EF9)
На этой странице описаны изменения API и поведения, которые могут нарушить обновление существующих приложений с EF Core 8 до EF Core 9. Проверьте предыдущие критические изменения при обновлении из более ранней версии EF Core:
- Критические изменения в EF Core 8
- Критические изменения в EF Core 7
- Критические изменения в EF Core 6
Целевая платформа
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 Core. Рекомендуемое решение — , чтобы создать отдельный набор миграций для каждого поставщика.
- Миграции создаются или выбираются динамически путем замены некоторых служб 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[]?
Старое поведение
Функция 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
Старое поведение
Ранее можно было создать SqlFunctionExpression
с другим числом аргументов и аргументами распространения значений NULL.
Новое поведение
Начиная с EF Core 9.0, EF теперь выдает, если число аргументов и аргументов распространения null не совпадает.
Почему
Не совпадающее число аргументов и аргументов распространения null может привести к непредвиденному поведению.
Устранение проблем
Убедитесь, что число argumentsPropagateNullability
элементов совпадает с числом arguments
элементов. Если сомнение используется false
для аргумента NULL.
ToString()
Метод теперь возвращает пустую строку для null
экземпляров
Старое поведение
Ранее EF вернул несогласованные результаты для ToString()
метода при значении null
аргумента. Например, ToString()
для свойства с bool?
возвращаемым null
значением, но для выражений, не являющихся свойствамиnull
, значение bool?
которого было возвращеноnull
True
. Поведение также было несогласовано для других типов данных, например 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.Memory
Microsoft.Extensions.Configuration.Abstractions
Microsoft.Extensions.Logging
и Microsoft.Extensions.DependencyModel
из общей платформы, поэтому эти сборки обычно не будут развертываться с приложением.
Новое поведение
Хотя EF Core 9.0 по-прежнему поддерживает net8.0, он теперь ссылается на версии System.Text.Json
9.0.x , Microsoft.Extensions.Caching.Memory
Microsoft.Extensions.Configuration.Abstractions
Microsoft.Extensions.Logging
и .Microsoft.Extensions.DependencyModel
Приложения, предназначенные для net8.0, не смогут использовать общую платформу, чтобы избежать развертывания этих сборок.
Почему
Соответствующие версии зависимостей содержат последние исправления безопасности и их использование упрощает модель обслуживания для EF Core.
Устранение проблем
Измените приложение на целевой net9.0, чтобы получить предыдущее поведение.
Критические изменения в Azure Cosmos DB
Обширная работа прошла в том, чтобы сделать поставщик Azure Cosmos DB лучше в 9.0. Изменения включают ряд критически важных изменений; Если вы обновляете существующее приложение, внимательно ознакомьтесь со следующими сведениями.
Изменения высокой степени влияния
Дискриминатор теперь именуется $type
вместо Discriminator
Старое поведение
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 по умолчанию
Старое поведение
Ранее EF вставляет дискриминационные значения типа сущности в id
свойство документа. Например, если вы сохранили тип сущности со свойством Blog
Id
, содержащим 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
сопоставляется с ключом
Старое поведение
Ранее 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 больше не поддерживается
Старое поведение
Ранее вызов синхронных методов, таких 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 напрямую
Старое поведение
Ранее 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, как показано выше.
Неопределенные результаты теперь автоматически фильтруются из результатов запроса
Старое поведение
Ранее 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();
Неправильно переведенные запросы больше не переводятся
Старое поведение
Ранее 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
теперь бросает вместо того, чтобы игнорировать
Старое поведение
Ранее вызовы HasIndex были проигнорированы поставщиком EF Cosmos DB.
Новое поведение
Теперь поставщик создает исключение, если HasIndex задано.
Почему
В Azure Cosmos DB все свойства индексируются по умолчанию, и индексирование не требуется указывать. Хотя можно определить настраиваемую политику индексирования, она в настоящее время не поддерживается EF и может выполняться через портал Azure без поддержки EF. Так как HasIndex звонки ничего не делают, они больше не допускаются.
Устранение проблем
Удалите все вызовы HasIndex.
IncludeRootDiscriminatorInJsonId
был HasRootDiscriminatorInJsonId
переименован в после версии 9.0.0-rc.2
Старое поведение
API IncludeRootDiscriminatorInJsonId
появился в версии 9.0.0 rc.1.
Новое поведение
Для окончательного выпуска EF Core 9.0 API был переименован в HasRootDiscriminatorInJsonId
Почему
Другой связанный API был переименован, чтобы начать Has
с, а не Include
, поэтому он был переименован для согласованности.
Устранение проблем
Если код использует IncludeRootDiscriminatorInJsonId
API, просто измените его на ссылку HasRootDiscriminatorInJsonId
.