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

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

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

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

Итоги

Критические изменения Воздействие
Encrypt По true умолчанию для подключений SQL Server Высокая
Некоторые предупреждения снова будут вызывать исключения по умолчанию Высокая
Теперь для таблиц SQL Server с триггерами или определенными вычисляемых столбцами требуется специальная конфигурация EF Core Высокая
Таблицы SQLite с триггерами AFTER и виртуальными таблицами теперь требуют специальной конфигурации EF Core Высокая
Потерянные зависимости от необязательных связей не удаляются автоматически Средняя
Каскадное удаление настраивается между таблицами при использовании сопоставления TPT с SQL Server Средняя
Более высокая вероятность ошибок занята или заблокирована в SQLite, если не используется ведение журнала на основе записи. Средняя
Возможно, необходимо настроить ключевые свойства с помощью средства сравнения значений поставщика. Низкая
Проверка ограничений и других аспектов таблицы теперь настроены в таблице Низкая
Навигации из новых сущностей в удаленные сущности не исправлены Низкая
Использование FromSqlRaw и связанные методы из неправильного поставщика вызывает Низкая
Шаблон больше OnConfiguring не вызывает IsConfigured Низкая

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

Encrypt По true умолчанию для подключений SQL Server

Проблема отслеживания: SqlClient #1210

Внимание

Это серьезное критическое изменение в пакете Microsoft.Data.SqlClient . Нет ничего, что можно сделать в EF Core, чтобы отменить изменения или устранить это изменение. Обратитесь к репозиторию GitHub Microsoft.Data.SqlClient или обратитесь к специалисту служба поддержки Майкрософт для получения дополнительных вопросов или справки.

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

SqlClient строка подключения используетсяEncrypt=False по умолчанию. Это позволяет подключаться к компьютерам разработки, где локальный сервер не имеет допустимого сертификата.

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

SqlClient строка подключения используетсяEncrypt=True по умолчанию. Это означает следующее.

  • Сервер должен быть настроен с допустимым сертификатом
  • Клиент должен доверять этому сертификату.

Если эти условия не выполнены, SqlException будет создано исключение. Например:

Соединение с сервером было успешно установлено, но при входе в систему произошла ошибка. (поставщик: поставщик SSL, ошибка: 0 — цепочка сертификатов была выдана центром сертификации, который не является доверенным.)

Почему

Это изменение было сделано, чтобы убедиться, что по умолчанию подключение безопасно или приложение не сможет подключиться.

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

Существует три способа продолжить:

  1. Установите действительный сертификат на сервере. Обратите внимание, что это задействованный процесс и требует получения сертификата и обеспечения его подписывания центром, доверенным клиентом.
  2. Если у сервера есть сертификат, но он не является доверенным клиентом, то TrustServerCertificate=True разрешить обход обычных mechanims доверия.
  3. Явно добавьте Encrypt=False в строка подключения.

Предупреждение

Параметры 2 и 3 покидают сервер в потенциально небезопасном состоянии.

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

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

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

В EF Core 6.0 ошибка в поставщике SQL Server означает, что некоторые предупреждения, настроенные для создания исключений по умолчанию, регистрируются, но не вызывают исключений. Эти предупреждения:

EventId Description
RelationalEventId.AmbientTransactionWarning Возможно, приложение ожидало, что внешняя транзакция будет использоваться, когда она фактически игнорируется.
RelationalEventId.IndexPropertiesBothMappedAndNotMappedToTable Индекс указывает свойства, которые сопоставляются и некоторые из которых не сопоставляются со столбцом в таблице.
RelationalEventId.IndexPropertiesMappedToNonOverlappingTables Индекс указывает свойства, которые сопоставляются со столбцами в не перекрывающихся таблицах.
RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables Внешний ключ указывает свойства, которые не сопоставляют с связанными таблицами.

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

Начиная с EF Core 7.0, эти предупреждения снова по умолчанию приводят к возникновению исключения.

Почему

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

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

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

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

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Ignore(RelationalEventId.AmbientTransactionWarning));

Теперь для таблиц SQL Server с триггерами или определенными вычисляемых столбцами требуется специальная конфигурация EF Core

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

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

Предыдущие версии поставщика SQL Server сохранили изменения с помощью менее эффективного метода, который всегда работал.

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

По умолчанию EF Core сохраняет изменения с помощью более эффективного метода; К сожалению, этот метод не поддерживается в SQL Server, если целевая таблица имеет триггеры базы данных или определенные типы вычисляемых столбцов. Дополнительные сведения см. в документации по SQL Server.

Почему

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

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

Начиная с EF Core 8.0, можно явно настроить использование или не предложение OUTPUT. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.UseSqlOutputClause(false));
}

В EF7 или более поздней версии, если целевая таблица имеет триггер, вы можете сообщить EF Core об этом, и EF отменить изменения к предыдущему, менее эффективному методу. Это можно сделать, настроив соответствующий тип сущности следующим образом:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.HasTrigger("SomeTrigger"));
}

Обратите внимание, что это на самом деле не делает EF Core создавать или управлять триггером каким-либо образом. В настоящее время он сообщает EF Core, что триггеры присутствуют в таблице. В результате можно использовать любое имя триггера. Указание триггера можно использовать для отменить изменения старого поведения, даже если в таблице нет триггера.

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

public class BlankTriggerAddingConvention : IModelFinalizingConvention
{
    public virtual void ProcessModelFinalizing(
        IConventionModelBuilder modelBuilder,
        IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
        {
            var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
            if (table != null
                && entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(table.Value) == null)
                && (entityType.BaseType == null
                    || entityType.GetMappingStrategy() != RelationalAnnotationNames.TphMappingStrategy))
            {
                entityType.Builder.HasTrigger(table.Value.Name + "_Trigger");
            }

            foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
            {
                if (entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(fragment.StoreObject) == null))
                {
                    entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger");
                }
            }
        }
    }
}

Используйте соглашение о вашем DbContext переопределении ConfigureConventions:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Add(_ => new BlankTriggerAddingConvention());
}

Это эффективно вызывает HasTrigger все таблицы модели, а не делать это вручную для каждой таблицы.

Таблицы SQLite с триггерами AFTER и виртуальными таблицами теперь требуют специальной конфигурации EF Core

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

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

Предыдущие версии поставщика SQLite сохранили изменения с помощью менее эффективной техники, которая всегда работала.

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

По умолчанию EF Core сохраняет изменения с помощью более эффективного метода с помощью предложения RETURNING. К сожалению, этот метод не поддерживается в SQLite, если целевая таблица имеет триггеры базы данных AFTER, является виртуальной или если используются старые версии SQLite. Дополнительные сведения см. в документации по SQLite.

Почему

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

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

В EF Core 8.0 UseSqlReturningClause метод был введен явным образом отменить изменения назад к более старой, менее эффективной ВЕРСИИ SQL. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.UseSqlReturningClause(false));
}

Если вы по-прежнему используете EF Core 7.0, можно отменить изменения старый механизм для всего приложения, вставив следующий код в конфигурацию контекста:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlite(...)
        .ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();

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

Потерянные зависимости от необязательных связей не удаляются автоматически

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

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

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

Необязательный зависимый может быть удален из субъекта путем установки внешнего ключа на значение NULL или очистки навигации к нему или из него. В EF Core 6.0 это приведет к удалению зависимости при настройке связи для каскадного удаления.

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

Начиная с EF Core 7.0, зависимость больше не удаляется. Обратите внимание, что если субъект удаляется, то зависимые по-прежнему будут удалены, так как каскадные удаления настроены для связи.

Почему

Зависимые могут существовать без каких-либо связей с субъектом, поэтому удаление сущности не должно привести к удалению сущности.

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

Зависимость может быть явно удалена:

context.Remove(blog);

Можно SaveChanges переопределить или перехватить для удаления зависимых без ссылки на субъект. Например:

context.SavingChanges += (c, _) =>
    {
        foreach (var entry in ((DbContext)c!).ChangeTracker
            .Entries<Blog>()
            .Where(e => e.State == EntityState.Modified))
        {
            if (entry.Reference(e => e.Author).CurrentValue == null)
            {
                entry.State = EntityState.Deleted;
            }
        }
    };

Каскадное удаление настраивается между таблицами при использовании сопоставления TPT с SQL Server

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

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

При сопоставлении иерархии наследования с помощью стратегии TPT базовая таблица должна содержать строку для каждой сохраненной сущности независимо от фактического типа этой сущности. Удаление строки в базовой таблице должно удалять строки во всех остальных таблицах. EF Core настраивает каскадные удаления для этого.

В EF Core 6.0 ошибка в поставщике баз данных SQL Server означает, что эти каскадные удаления не создаются.

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

Начиная с EF Core 7.0, каскадные удаления теперь создаются для SQL Server так же, как и для других баз данных.

Почему

Каскадные удаления из базовой таблицы в вложенные таблицы в TPT позволяют удалить сущность, удалив ее строку в базовой таблице.

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

В большинстве случаев это изменение не должно вызывать никаких проблем. Однако SQL Server очень ограничительно, если между таблицами настроено несколько каскадных действий. Это означает, что если существует каскадная связь между таблицами в сопоставлении TPT, SQL Server может создать следующую ошибку:

Microsoft.Data.SqlClient.SqlException: инструкция DELETE конфликтует с ограничением REFERENCE "FK_Blogs_Люди_OwnerId". Конфликт произошел в базе данных Scratch, таблица dbo.Blogs, столбец OwnerId. Выполнение данной инструкции было прервано.

Например, эта модель создает цикл каскадных связей:

[Table("FeaturedPosts")]
public class FeaturedPost : Post
{
    public int ReferencePostId { get; set; }
    public Post ReferencePost { get; set; } = null!;
}

[Table("Posts")]
public class Post
{
    public int Id { get; set; }
    public string? Title { get; set; }
    public string? Content { get; set; }
}

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

modelBuilder
    .Entity<FeaturedPost>()
    .HasOne(e => e.ReferencePost)
    .WithMany()
    .OnDelete(DeleteBehavior.ClientCascade);

Или изменить неявную связь, созданную для сопоставления TPT:

modelBuilder
    .Entity<FeaturedPost>()
    .HasOne<Post>()
    .WithOne()
    .HasForeignKey<FeaturedPost>(e => e.Id)
    .OnDelete(DeleteBehavior.ClientCascade);

Более высокая вероятность ошибок занята или заблокирована в SQLite, если не используется ведение журнала на основе записи.

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

Предыдущие версии поставщика SQLite сохранили изменения с помощью менее эффективной методики, которая могла автоматически повторить попытку, когда таблица была заблокирована или занята, и ведение журнала на основе записи (WAL) не было включено.

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

По умолчанию EF Core сохраняет изменения с помощью более эффективного метода с помощью предложения RETURNING. К сожалению, этот метод не может автоматически повторяться при занятой или заблокированной. В многопоточное приложение (например, веб-приложение), не использующее ведение журнала на основе записи, обычно возникают эти ошибки.

Почему

Упрощение и повышение производительности, связанные с новым методом, достаточно значительны, чтобы по умолчанию им было важно привлечь пользователей. Базы данных, созданные EF Core, также позволяют выполнять ведение журнала накануне записи по умолчанию. Команда SQLite также рекомендует включить ведение журнала перед записью по умолчанию.

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

Если это возможно, необходимо включить ведение журнала впереди записи в базе данных. Если база данных была создана EF, это уже должно быть так. Если нет, вы можете включить ведение журнала перед записью, выполнив следующую команду.

PRAGMA journal_mode = 'wal';

Если, по какой-то причине, вы не можете включить ведение журнала на основе записи, можно отменить изменения старый механизм для всего приложения, вставив следующий код в конфигурацию контекста:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlite(...)
        .ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();

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

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

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

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

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

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

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

Почему

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

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

Настройка сравнения значений поставщика. Например, рассмотрим случай, когда объект value используется в качестве ключа, а для сравнения этого ключа используется сравнение строк без учета регистра:

var blogKeyComparer = new ValueComparer<BlogKey>(
    (l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
    v => v.Id.ToUpper().GetHashCode(),
    v => v);

var blogKeyConverter = new ValueConverter<BlogKey, string>(
    v => v.Id,
    v => new BlogKey(v));

modelBuilder.Entity<Blog>()
    .Property(e => e.Id).HasConversion(
        blogKeyConverter, blogKeyComparer);

Значения базы данных (строки) не могут напрямую использовать средство сравнения, определенное для BlogKey типов. Поэтому для сравнения строк без учета регистра необходимо настроить средство сравнения поставщиков:

var caseInsensitiveComparer = new ValueComparer<string>(
    (l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
    v => v.ToUpper().GetHashCode(),
    v => v);

var blogKeyComparer = new ValueComparer<BlogKey>(
    (l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
    v => v.Id.ToUpper().GetHashCode(),
    v => v);

var blogKeyConverter = new ValueConverter<BlogKey, string>(
    v => v.Id,
    v => new BlogKey(v));

modelBuilder.Entity<Blog>()
    .Property(e => e.Id).HasConversion(
        blogKeyConverter, blogKeyComparer, caseInsensitiveComparer);

Проверка ограничений и других аспектов таблицы теперь настроены в таблице

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

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

В EF Core 6.0 HasCheckConstraintHasCommentи IsMemoryOptimized вызывались непосредственно в построителе типов сущностей. Например:

modelBuilder
    .Entity<Blog>()
    .HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023");

modelBuilder
    .Entity<Blog>()
    .HasComment("It's my table, and I'll delete it if I want to.");

modelBuilder
    .Entity<Blog>()
    .IsMemoryOptimized();

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

Начиная с EF Core 7.0, эти методы вместо этого вызываются в построителе таблиц:

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023"));

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.HasComment("It's my table, and I'll delete it if I want to."));

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.IsMemoryOptimized());

Существующие методы помечены как Obsolete. В настоящее время они имеют то же поведение, что и новые методы, но будут удалены в будущем выпуске.

Почему

Эти аспекты применяются только к таблицам. Они не будут применяться к сопоставленным представлениям, функциям или хранимым процедурам.

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

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

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

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

В EF Core 6.0, когда новая сущность отслеживается либо из запроса отслеживания, либо путем присоединения ее к DbContextобъекту, а затем переходы к связанным сущностям в Deleted состоянии исправляются.

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

Начиная с EF Core 7.0 навигации в сущности и из Deleted них не фиксируются.

Почему

После того как сущность помечается как Deleted редко имеет смысл связать ее с неудалёнными сущностями.

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

Запрос или присоединение сущностей перед маркировкой сущностей как Deletedсущностей или вручную задайте свойства навигации для удаленной сущности и из нее.

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

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

В EF Core 6.0 использование метода расширения Azure Cosmos DB FromSqlRaw при использовании реляционного поставщика или метода реляционного FromSqlRaw расширения при использовании поставщика Azure Cosmos DB может автоматически завершиться ошибкой. Аналогичным образом использование реляционных методов в поставщике в памяти является безмолвным no-op.

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

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

Почему

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

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

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

var result = CosmosQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();

Или сделайте так:

var result = RelationalQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();

Шаблон больше OnConfiguring не вызывает IsConfigured

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

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

В EF Core 6.0 тип, сформированный из существующей базы данных, DbContext содержал вызов IsConfigured. Например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
        optionsBuilder.UseNpgsql("MySecretConnectionString");
    }
}

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

Начиная с EF Core 7.0 вызов IsConfigured больше не включен.

Почему

В некоторых случаях поставщик базы данных настраивается внутри DbContext, но только в том случае, если контекст еще не настроен. Вместо этого, оставляя OnConfiguring здесь, делает его более вероятным, что строка подключения, содержащий конфиденциальную информацию, остается в коде, несмотря на предупреждение во время компиляции. Таким образом, дополнительный безопасный и чистый код от удаления этого считался полезным, особенно учитывая, что --no-onconfiguring флаг (.NET CLI) или -NoOnConfiguring (консоль диспетчер пакетов Visual Studio) можно использовать для предотвращения формирования шаблонов OnConfiguring метода, и что настраиваемые шаблоны существуют, чтобы добавить обратноIsConfigured, если это действительно необходимо.

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

Любое из следующих:

  • --no-onconfiguring Используйте аргумент (.NET CLI) или -NoOnConfiguring (консоль диспетчер пакетов Visual Studio) при создании шаблонов из существующей базы данных.
  • Настройте шаблоны T4 для добавления обратного вызова IsConfigured.