Критические изменения в EF Core 8 (EF8)
На этой странице описаны изменения API и поведения, которые могут нарушить обновление существующих приложений с EF Core 7 до EF Core 8. Проверьте предыдущие критические изменения при обновлении из более ранней версии EF Core:
Целевая платформа
EF Core 8 предназначен для .NET 8. Приложения, предназначенные для более старых версий .NET, .NET Core и платформа .NET Framework, необходимо обновить до целевого .NET 8.
Итоги
Изменения высокой степени влияния
Contains
В запросах LINQ может перестать работать с более старыми версиями SQL Server
Старое поведение
Ранее, когда Contains
оператор использовался в запросах LINQ с параметризованным списком значений, EF создал SQL, который был неэффективным, но работал на всех версиях SQL Server.
Новое поведение
Начиная с EF Core 8.0 EF теперь создает SQL, который более эффективен, но не поддерживается в SQL Server 2014 и ниже.
Обратите внимание, что более новые версии SQL Server могут быть настроены с более старым уровнем совместимости, что также делает их несовместимыми с новым SQL. Это также может произойти с базой данных SQL Azure, которая была перенесена из предыдущего локального экземпляра SQL Server, на котором выполняется старый уровень совместимости.
Почему
Предыдущий SQL, созданный EF Core для Contains
вставки параметризованных значений в качестве констант в SQL. Например, следующий запрос LINQ:
var names = new[] { "Blog1", "Blog2" };
var blogs = await context.Blogs
.Where(b => names.Contains(b.Name))
.ToArrayAsync();
... будет переведен в следующий SQL:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')
Такая вставка константных значений в SQL создает множество проблем с производительностью, побеждая кэширование плана запросов и вызывая ненужные вытеснения других запросов. Новый перевод EF Core 8.0 использует функцию SQL Server OPENJSON
для передачи значений в виде массива JSON. Это решает проблемы с производительностью, присущие предыдущему методу; OPENJSON
Однако функция недоступна в SQL Server 2014 и ниже.
Дополнительные сведения об этом изменении см. в этой записи блога.
Устранение проблем
Если база данных — SQL Server 2016 (13.x) или более новая, или если вы используете SQL Azure, проверьте настроенный уровень совместимости базы данных с помощью следующей команды:
SELECT name, compatibility_level FROM sys.databases;
Если уровень совместимости ниже 130 (SQL Server 2016), попробуйте изменить его на новое значение (документация).
В противном случае, если версия базы данных действительно старше SQL Server 2016 или установлена на старый уровень совместимости, который вы не можете изменить по какой-то причине, настройте EF Core, чтобы вернуться к более старому, менее эффективному SQL следующим образом:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));
Перечисления в JSON хранятся в виде ветвей вместо строк по умолчанию
Старое поведение
В EF7 перечисления, сопоставленные с JSON, хранятся по умолчанию в виде строковых значений в документе JSON.
Новое поведение
Начиная с EF Core 8.0, EF теперь по умолчанию сопоставляет перечисления с целыми значениями в документе JSON.
Почему
EF всегда сопоставляет перечисления с числовым столбцом в реляционных базах данных по умолчанию. Так как EF поддерживает запросы, в которых значения из JSON взаимодействуют со значениями из столбцов и параметров, важно, чтобы значения в JSON соответствовали значениям в столбце, отличном от JSON.
Устранение проблем
Чтобы продолжить использование строк, настройте свойство перечисления с помощью преобразования. Например:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Property(e => e.Status).HasConversion<string>();
}
Или для всех свойств типа перечисления::
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<StatusEnum>().HaveConversion<string>();
}
Изменения средней степени влияния
SQL Server date
и time
теперь шаблон в .NET DateOnly
и TimeOnly
Старое поведение
Ранее при создании шаблонов базы данных SQL Server с date
или time
столбцами EF создаст свойства сущности с типами DateTime и TimeSpan.
Новое поведение
Начиная с EF Core 8.0 и date
time
шаблонов как DateOnly и TimeOnly.
Почему
DateOnly и TimeOnly были представлены в .NET 6.0 и идеально подходят для сопоставления типов даты и времени базы данных. DateTime В частности, содержит компонент времени, который не используется и может вызвать путаницу при сопоставлении с ним date
и TimeSpan представляет интервал времени , возможно, включая дни , а не время дня, в течение которого происходит событие. Использование новых типов предотвращает ошибки и путаницу и обеспечивает ясность намерения.
Устранение проблем
Это изменение влияет только на пользователей, которые регулярно повторно объединяют базу данных в модель кода EF (поток "база данных-first").
Рекомендуется реагировать на это изменение, изменив код для использования новых шаблонов DateOnly и TimeOnly типов. Однако, если это невозможно, можно изменить шаблоны шаблонов, чтобы вернуться к предыдущему сопоставлению. Для этого настройте шаблоны, как описано на этой странице. Затем измените EntityType.t4
файл, найдите, где создаются свойства сущности (поиск property.ClrType
) и измените код следующим образом:
var clrType = property.GetColumnType() switch
{
"date" when property.ClrType == typeof(DateOnly) => typeof(DateTime),
"date" when property.ClrType == typeof(DateOnly?) => typeof(DateTime?),
"time" when property.ClrType == typeof(TimeOnly) => typeof(TimeSpan),
"time" when property.ClrType == typeof(TimeOnly?) => typeof(TimeSpan?),
_ => property.ClrType
};
usings.AddRange(code.GetRequiredUsings(clrType));
var needsNullable = Options.UseNullableReferenceTypes && property.IsNullable && !clrType.IsValueType;
var needsInitializer = Options.UseNullableReferenceTypes && !property.IsNullable && !clrType.IsValueType;
#>
public <#= code.Reference(clrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
Логические столбцы с созданным значением базы данных больше не создаются как пустые
Старое поведение
Ранее столбцы, не допускающие bool
значения NULL, с ограничением по умолчанию базы данных, были шаблонированы как свойства, допускающие bool?
значение NULL.
Новое поведение
Начиная с EF Core 8.0 столбцы, не допускающие bool
значения NULL, всегда создаются в виде непустых свойств.
Почему
Свойство bool
не будет отправляться в базу данных, если это значение равно false
clR по умолчанию. Если база данных имеет значение true
по умолчанию для столбца, то даже если значение свойства равно false
, значение в базе данных заканчивается как true
. Однако в EF8 sentinel используется для определения возможности изменения свойства. Это делается автоматически для bool
свойств с созданным базой данных значением true
, что означает, что больше не требуется создавать шаблон свойств как допускающие значение NULL.
Устранение проблем
Это изменение влияет только на пользователей, которые регулярно повторно объединяют базу данных в модель кода EF (поток "база данных-first").
Рекомендуется реагировать на это изменение, изменив код, чтобы использовать ненулевое свойство bool. Однако, если это невозможно, можно изменить шаблоны шаблонов, чтобы вернуться к предыдущему сопоставлению. Для этого настройте шаблоны, как описано на этой странице. Затем измените EntityType.t4
файл, найдите, где создаются свойства сущности (поиск property.ClrType
) и измените код следующим образом:
#>
var propertyClrType = property.ClrType != typeof(bool)
|| (property.GetDefaultValueSql() == null && property.GetDefaultValue() != null)
? property.ClrType
: typeof(bool?);
#>
public <#= code.Reference(propertyClrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
<#
Изменения низкой степени влияния
Теперь методы SQLite Math
преобразуют в SQL
Поведение в предыдущих версиях
Ранее только методы Math
Abs, Max, Min и Round были переведены в SQL. Все остальные члены будут оцениваться на клиенте, если они появились в окончательном выражении Select запроса.
Новое поведение
В EF Core 8.0 все Math
методы с соответствующими математическими функциями SQLite переводятся в SQL.
Эти математические функции включены в собственной библиотеке SQLite, которую мы предоставляем по умолчанию (с помощью нашей зависимости от пакета NuGet SQLitePCLRaw.bundle_e_sqlite3). Они также были включены в библиотеке, предоставляемой SQLitePCLRaw.bundle_e_sqlcipher. Если вы используете одну из этих библиотек, приложение не должно влиять на это изменение.
Однако существует вероятность того, что приложения, включая собственную библиотеку SQLite, могут не включать математические функции. В этих случаях Math
методы будут переведены в SQL и не возникают таких ошибок функции при выполнении.
Почему
SQLite добавил встроенные математические функции в версии 3.35.0. Несмотря на то, что они отключены по умолчанию, они становятся достаточно распространенными, что мы решили предоставить переводы по умолчанию для них в нашем поставщике EF Core SQLite.
Мы также сотрудничали с Эриком Приемником в проекте SQLitePCLRaw, чтобы включить математические функции во всех собственных библиотеках SQLite, предоставляемых в рамках этого проекта.
Устранение проблем
Самый простой способ исправить разрывы — по возможности включить математическую функцию — это собственная библиотека SQLite, указав параметр времени компиляции SQLITE_ENABLE_MATH_FUNCTIONS .
Если вы не управляете компиляцией собственной библиотеки, можно также исправить разрывы, создав функции самостоятельно во время выполнения с помощью API Microsoft.Data.Sqlite .
sqliteConnection
.CreateFunction<double, double, double>(
"pow",
Math.Pow,
isDeterministic: true);
Кроме того, можно принудительно выполнить оценку клиента, разделив выражение Select на две части, разделенные по отдельности AsEnumerable
.
// Before
var query = dbContext.Cylinders
.Select(
c => new
{
Id = c.Id
// May throw "no such function: pow"
Volume = Math.PI * Math.Pow(c.Radius, 2) * c.Height
});
// After
var query = dbContext.Cylinders
// Select the properties you'll need from the database
.Select(
c => new
{
c.Id,
c.Radius,
c.Height
})
// Switch to client-eval
.AsEnumerable()
// Select the final results
.Select(
c => new
{
Id = c.Id,
Volume = Math.PI * Math.Pow(c.Radius, 2) * c.Height
});
ITypeBase заменяет IEntityType в некоторых API
Старое поведение
Ранее все сопоставленные структурные типы были типами сущностей.
Новое поведение
При внедрении сложных типов в EF8 некоторые API, которые ранее использовались IEntityType
, ITypeBase
чтобы API можно было использовать с сущностями или сложными типами. Сюда входит следующее:
IProperty.DeclaringEntityType
теперь устарел иIProperty.DeclaringType
вместо этого следует использовать.IEntityTypeIgnoredConvention
теперь устарел иITypeIgnoredConvention
вместо этого следует использовать.IValueGeneratorSelector.Select
теперь принимает тоITypeBase
, что может быть, но не должно бытьIEntityType
.
Почему
При внедрении сложных типов в EF8 эти API можно использовать с либо IEntityType
IComplexType
.
Устранение проблем
Старые API устарели, но не будут удалены до EF10. Код следует обновить, чтобы использовать новые API ASAP.
Выражения ValueConverter и ValueComparer должны использовать общедоступные API для скомпилированной модели.
Старое поведение
ValueConverter
Ранее и ValueComparer
определения не были включены в скомпилированную модель и поэтому могут содержать произвольный код.
Новое поведение
EF теперь извлекает выражения из ValueConverter
объектов и ValueComparer
включает эти C# в скомпилированную модель. Это означает, что эти выражения должны использовать только общедоступный API.
Почему
Команда EF постепенно перемещает больше конструкций в скомпилированную модель для поддержки использования EF Core с AOT в будущем.
Устранение проблем
Сделайте API- интерфейсы, используемые средством сравнения общедоступным. Например, рассмотрим этот простой преобразователь:
public class MyValueConverter : ValueConverter<string, byte[]>
{
public MyValueConverter()
: base(v => ConvertToBytes(v), v => ConvertToString(v))
{
}
private static string ConvertToString(byte[] bytes)
=> ""; // ... TODO: Conversion code
private static byte[] ConvertToBytes(string chars)
=> Array.Empty<byte>(); // ... TODO: Conversion code
}
Чтобы использовать этот преобразователь в скомпилированной модели с EF8, ConvertToString
ConvertToBytes
необходимо сделать общедоступными методы. Например:
public class MyValueConverter : ValueConverter<string, byte[]>
{
public MyValueConverter()
: base(v => ConvertToBytes(v), v => ConvertToString(v))
{
}
public static string ConvertToString(byte[] bytes)
=> ""; // ... TODO: Conversion code
public static byte[] ConvertToBytes(string chars)
=> Array.Empty<byte>(); // ... TODO: Conversion code
}
ИсключениеFromMigrations больше не исключает другие таблицы в иерархии TPC
Старое поведение
Ранее использование ExcludeFromMigrations
таблицы в иерархии TPC также исключит другие таблицы в иерархии.
Новое поведение
Начиная с EF Core 8.0, ExcludeFromMigrations
не влияет на другие таблицы.
Почему
Старое поведение было ошибкой и препятствовало использованию миграций для управления иерархиями в проектах.
Устранение проблем
Используйте ExcludeFromMigrations
явным образом любую другую таблицу, которая должна быть исключена.
Нетеневые целые ключи сохраняются в документах Cosmos
Старое поведение
Ранее нетеневые целые свойства, соответствующие критериям синтезированного ключевого свойства, не сохранялись в документе JSON, но были повторно синтезированы на выходе.
Новое поведение
Начиная с EF Core 8.0 эти свойства теперь сохраняются.
Почему
Старое поведение было ошибкой и препятствовало свойствам, которые соответствуют синтезированные ключевые критерии сохранения в Cosmos.
Устранение проблем
Исключите свойство из модели , если его значение не должно быть сохранено.
Кроме того, вы можете полностью отключить это поведение, задав Microsoft.EntityFrameworkCore.Issue31664
для параметра AppContext переключатель true
AppContext для потребителей библиотеки для получения дополнительных сведений .
AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue31664", isEnabled: true);
Реляционная модель создается в скомпилированной модели
Старое поведение
Ранее реляционная модель вычислялась во время выполнения даже при использовании скомпилированной модели.
Новое поведение
Начиная с EF Core 8.0 реляционная модель является частью созданной скомпилированной модели. Однако для особенно больших моделей созданный файл может завершиться сбоем компиляции.
Почему
Это было сделано для дальнейшего улучшения времени запуска.
Устранение проблем
Измените созданный *ModelBuilder.cs
файл и удалите строку AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel());
, а также метод CreateRelationalModel()
.
Формирование шаблонов может создавать разные имена навигации
Старое поведение
Ранее при создании DbContext
шаблонов типов сущностей из существующей базы данных имена навигации для связей иногда были производными от общего префикса нескольких имен столбцов внешнего ключа.
Новое поведение
Начиная с EF Core 8.0 общие префиксы имен столбцов из составного внешнего ключа больше не используются для создания имен навигации.
Почему
Это неясное правило именования, которое иногда создает очень плохие имена, например, S
или Student_
даже просто _
. Без этого правила странные имена больше не создаются, а соглашения об именовании для навигаций также упрощаются, тем самым упрощая понимание и прогнозирование создания имен.
Устранение проблем
В EF Core Power Tools есть возможность создавать навигации в старом режиме. Кроме того, созданный код можно полностью настроить с помощью шаблонов T4. Это можно использовать для примера свойств внешнего ключа связей шаблонов и использовать любое правило, соответствующее коду, чтобы создать нужные имена навигации.
Теперь дискриминаторы имеют максимальную длину
Старое поведение
Ранее дискриминационные столбцы, созданные для сопоставления наследования TPH, были настроены как nvarchar(max)
в SQL Server/Azure SQL или эквивалентном несвязанном типе строки в других базах данных.
Новое поведение
Начиная с EF Core 8.0, дискриминационные столбцы создаются с максимальной длиной, которая охватывает все известные дискриминационные значения. EF создаст миграцию, чтобы внести это изменение. Однако если столбец дискриминатора ограничен каким-то образом , например, как часть индекса, то AlterColumn
созданный миграцией может завершиться ошибкой.
Почему
nvarchar(max)
столбцы являются неэффективными и ненужными, если длина всех возможных значений известна.
Устранение проблем
Размер столбца можно сделать явным образом несвязанным:
modelBuilder.Entity<Foo>()
.Property<string>("Discriminator")
.HasMaxLength(-1);
Значения ключей SQL Server сравниваются без учета регистра
Старое поведение
Ранее при отслеживании сущностей с помощью строковых ключей с поставщиками баз данных SQL Server или Azure значения ключей сравнивались с помощью стандартного порядкового сравнения с учетом регистра .NET.
Новое поведение
Начиная с EF Core 8.0, значения ключей строк SQL Server и Azure SQL сравниваются с использованием нечувствительного порядкового сравнения регистра .NET по умолчанию.
Почему
По умолчанию SQL Server использует нечувствительные сравнения регистра при сравнении значений внешнего ключа для совпадений со значениями ключей субъекта. Это означает, что если EF использует сравнения с учетом регистра, он может не подключать внешний ключ к основному ключу, когда он должен.
Устранение проблем
Сравнения с учетом регистра можно использовать, задав настраиваемый ValueComparer
параметр. Например:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var comparer = new ValueComparer<string>(
(l, r) => string.Equals(l, r, StringComparison.Ordinal),
v => v.GetHashCode(),
v => v);
modelBuilder.Entity<Blog>()
.Property(e => e.Id)
.Metadata.SetValueComparer(comparer);
modelBuilder.Entity<Post>(
b =>
{
b.Property(e => e.Id).Metadata.SetValueComparer(comparer);
b.Property(e => e.BlogId).Metadata.SetValueComparer(comparer);
});
}
Несколько вызовов AddDbContext применяются в другом порядке
Старое поведение
Ранее, когда несколько вызовов AddDbContext
AddDbContextPool
или были выполнены с одним и тем же типом контекста, AddDbContextFactory
AddPooledDbContextFactor
но конфликтующей конфигурацией, первая выиграла.
Новое поведение
Начиная с EF Core 8.0 конфигурация из последнего вызова будет иметь приоритет.
Почему
Это было изменено, чтобы соответствовать новому методу ConfigureDbContext
, который можно использовать для добавления конфигурации до или после Add*
методов.
Устранение проблем
Измените порядок вызовов Add*
.
EntityTypeAttributeConventionBase заменено TypeAttributeConventionBase
Новое поведение
В EF Core 8.0 EntityTypeAttributeConventionBase
переименован в TypeAttributeConventionBase
.
Почему
TypeAttributeConventionBase
представляет функциональность лучше, так как теперь ее можно использовать для сложных типов и типов сущностей.
Устранение проблем
Замените EntityTypeAttributeConventionBase
использование TypeAttributeConventionBase
на .