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


Критические изменения в 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 Высокая
Перечисления в JSON хранятся в виде ветвей вместо строк по умолчанию Высокая
SQL Server date и time теперь шаблон в .NET DateOnly и TimeOnly Средняя
Логические столбцы с созданным значением базы данных больше не создаются как пустые Средняя
Теперь методы SQLite Math преобразуют в SQL Низкая
ITypeBase заменяет IEntityType в некоторых API Низкая
Выражения ValueGenerator должны использовать общедоступные API Низкая
ИсключениеFromMigrations больше не исключает другие таблицы в иерархии TPC Низкая
Нетеневые целые ключи сохраняются в документах Cosmos Низкая
Реляционная модель создается в скомпилированной модели Низкая
Формирование шаблонов может создавать разные имена навигации Низкая
Теперь дискриминаторы имеют максимальную длину Низкая
Значения ключей SQL Server сравниваются без учета регистра Низкая
Несколько вызовов AddDbContext применяются в другом порядке Низкая
EntityTypeAttributeConventionBase заменено TypeAttributeConventionBase Низкая

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

Contains В запросах LINQ может перестать работать с более старыми версиями SQL Server

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

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

Ранее, когда 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 хранятся в виде ветвей вместо строк по умолчанию

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

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

В 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

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

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

Ранее при создании шаблонов базы данных 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!;" : "" #>
<#

Логические столбцы с созданным значением базы данных больше не создаются как пустые

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

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

Ранее столбцы, не допускающие bool значения NULL, с ограничением по умолчанию базы данных, были шаблонированы как свойства, допускающие bool? значение NULL.

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

Начиная с EF Core 8.0 столбцы, не допускающие bool значения NULL, всегда создаются в виде непустых свойств.

Почему

Свойство bool не будет отправляться в базу данных, если это значение равно falseclR по умолчанию. Если база данных имеет значение 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

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

Поведение в предыдущих версиях

Ранее только методы 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

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

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

Ранее все сопоставленные структурные типы были типами сущностей.

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

При внедрении сложных типов в 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 для скомпилированной модели.

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

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

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

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

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

Ранее использование ExcludeFromMigrations таблицы в иерархии TPC также исключит другие таблицы в иерархии.

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

Начиная с EF Core 8.0, ExcludeFromMigrations не влияет на другие таблицы.

Почему

Старое поведение было ошибкой и препятствовало использованию миграций для управления иерархиями в проектах.

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

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

Нетеневые целые ключи сохраняются в документах Cosmos

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

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

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

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

Начиная с EF Core 8.0 эти свойства теперь сохраняются.

Почему

Старое поведение было ошибкой и препятствовало свойствам, которые соответствуют синтезированные ключевые критерии сохранения в Cosmos.

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

Исключите свойство из модели , если его значение не должно быть сохранено. Кроме того, вы можете полностью отключить это поведение, задав Microsoft.EntityFrameworkCore.Issue31664 для параметра AppContext переключатель trueAppContext для потребителей библиотеки для получения дополнительных сведений .

AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue31664", isEnabled: true);

Реляционная модель создается в скомпилированной модели

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

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

Ранее реляционная модель вычислялась во время выполнения даже при использовании скомпилированной модели.

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

Начиная с EF Core 8.0 реляционная модель является частью созданной скомпилированной модели. Однако для особенно больших моделей созданный файл может завершиться сбоем компиляции.

Почему

Это было сделано для дальнейшего улучшения времени запуска.

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

Измените созданный *ModelBuilder.cs файл и удалите строку AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel()); , а также метод CreateRelationalModel().

Формирование шаблонов может создавать разные имена навигации

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

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

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

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

Начиная с EF Core 8.0 общие префиксы имен столбцов из составного внешнего ключа больше не используются для создания имен навигации.

Почему

Это неясное правило именования, которое иногда создает очень плохие имена, например, Sили Student_даже просто _. Без этого правила странные имена больше не создаются, а соглашения об именовании для навигаций также упрощаются, тем самым упрощая понимание и прогнозирование создания имен.

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

В EF Core Power Tools есть возможность создавать навигации в старом режиме. Кроме того, созданный код можно полностью настроить с помощью шаблонов T4. Это можно использовать для примера свойств внешнего ключа связей шаблонов и использовать любое правило, соответствующее коду, чтобы создать нужные имена навигации.

Теперь дискриминаторы имеют максимальную длину

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

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

Ранее дискриминационные столбцы, созданные для сопоставления наследования 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 сравниваются без учета регистра

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

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

Ранее при отслеживании сущностей с помощью строковых ключей с поставщиками баз данных 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 применяются в другом порядке

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

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

Ранее, когда несколько вызовов AddDbContextAddDbContextPoolили были выполнены с одним и тем же типом контекста, AddDbContextFactory AddPooledDbContextFactor но конфликтующей конфигурацией, первая выиграла.

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

Начиная с EF Core 8.0 конфигурация из последнего вызова будет иметь приоритет.

Почему

Это было изменено, чтобы соответствовать новому методу ConfigureDbContext , который можно использовать для добавления конфигурации до или после Add* методов.

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

Измените порядок вызовов Add* .

EntityTypeAttributeConventionBase заменено TypeAttributeConventionBase

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

В EF Core 8.0 EntityTypeAttributeConventionBase переименован в TypeAttributeConventionBase.

Почему

TypeAttributeConventionBase представляет функциональность лучше, так как теперь ее можно использовать для сложных типов и типов сущностей.

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

Замените EntityTypeAttributeConventionBase использование TypeAttributeConventionBaseна .