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


Критические изменения в EF Core 6.0

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

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

EF Core 6.0 предназначен для .NET 6. Приложения, предназначенные для более старых версий .NET, .NET Core и платформа .NET Framework, должны быть предназначены для .NET 6 для использования EF Core 6.0.

Итоги

Критические изменения Воздействие
Вложенные необязательные зависимые объекты, совместно использующие таблицу и не имеющие обязательных свойств, не могут быть сохранены Высокая
При изменении владельца принадлежащей сущности теперь создается исключение Средняя
Azure Cosmos DB: связанные типы сущностей обнаруживаются как принадлежащие Средняя
SQLite: подключения в пуле Средняя
Связи "многие ко многим" без сопоставленных сущностей соединений теперь доступны для формирования шаблонов Средняя
Очищено сопоставление между DeleteBehavior и значениями ON DELETE Низкая
База данных в памяти проверяет, что обязательные свойства не содержат значений NULL Низкая
Удалено последнее предложение ORDER BY при присоединении коллекций Низкая
DbSet больше не реализует IAsyncEnumerable Низкая
Возвращаемый тип сущности TVF также сопоставляется с таблицей по умолчанию Низкая
Уникальность имени ограничения проверки теперь подтверждается Низкая
Добавлены интерфейсы метаданных IReadOnly и удалены методы расширения Низкая
IExecutionStrategy теперь является одноэлементной службой Низкая
SQL Server: дополнительные ошибки считаются временными Низкая
Azure Cosmos DB: дополнительные символы экранируются в значениях id Низкая
Некоторые одноэлементные службы теперь находятся в области Низкий*
Новый API кэширования для расширений, которые добавляют или заменяют службы Низкий*
Новая процедура инициализации модели моментального снимка и времени разработки Низкая
OwnedNavigationBuilder.HasIndex теперь возвращает другой тип Низкая
DbFunctionBuilder.HasSchema(null) переопределяет [DbFunction(Schema = "schema")] Низкая
Предварительно инициализированные переходы переопределяются значениями из запросов базы данных Низкая
Неизвестные строковые значения перечисления в базе данных не преобразуются в перечисление по умолчанию при запросе Низкая
DbFunctionBuilder.HasTranslation теперь предоставляет аргументы функции как IReadOnlyList, а не IReadOnlyCollection Низкая
Сопоставление таблиц по умолчанию не удаляется, если сущность сопоставлена с функцией с табличным значением Низкая
целевые объекты dotnet-ef .NET 6 Низкая
IModelCacheKeyFactory Для обработки кэширования во время разработки может потребоваться обновить реализации. Низкая
NavigationBaseIncludeIgnored Теперь ошибка по умолчанию Низкая

* Эти изменения представляют интерес для авторов поставщиков баз данных и расширений.

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

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

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

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

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

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ContactInfo ContactInfo { get; set; }
}

[Owned]
public class ContactInfo
{
    public string Phone { get; set; }
    public Address Address { get; set; }
}

[Owned]
public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

Ни одно из свойств в ContactInfo или Address не является обязательным, и все эти типы сущностей сопоставляются с одной и той же таблицей. Правила для необязательных зависимых объектов (в отличие от обязательных) гласят, что если все столбцы для ContactInfo имеют значение NULL, то при запросе владельца Customer не будет создаваться ни один экземпляр ContactInfo. Однако это также означает, что экземпляр Address не будет создан, даже если столбцы Address не равны NULL.

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

При попытке использовать эту модель будет создано следующее исключение:

System.InvalidOperationException: тип сущности ContactInfo представляет собой необязательный зависимый объект с общим доступом к таблицам, содержащий другие зависимые объекты без обязательных необщедоступных свойств, определяющих существование сущности. Если все свойства, допускающие значение NULL, содержат значение NULL в базе данных, то экземпляр объекта не будет создан в запросе, что приведет к потере вложенных зависимых элементов. Добавьте обязательное свойство, чтобы создать экземпляры со значениями NULL для других свойств или пометить входящую навигацию как обязательную, чтобы всегда создавать экземпляр.

Это предотвращает потерю данных при запросе и сохранении данных.

Почему

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

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

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

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

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        [Required]
        public Address Address { get; set; }
    }
    

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

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.OwnsOne(e => e.Address);
                b.Navigation(e => e.Address).IsRequired();
            });
    
  2. Убедитесь, что зависимый объект содержит по крайней мере одно обязательное свойство.

  3. Сопоставьте необязательные зависимые объекты с отдельной таблицей вместо предоставления общего доступа к таблице с субъектом. Например:

    modelBuilder.Entity<Customer>(
        b =>
            {
                b.ToTable("Customers");
                b.OwnsOne(e => e.Address, b => b.ToTable("CustomerAddresses"));
            });
    

Проблемы с необязательными зависимыми объектами и примеры решения этих проблем описаны в документации по новым возможностям EF Core 6.0.

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

При изменении владельца принадлежащей сущности теперь создается исключение

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

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

Можно было переназначать принадлежащую сущность другой сущности-владельцу.

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

Теперь это действие создаст исключение:

Свойство {entityType}.{property} является частью ключа и не может быть изменено или помечено как измененное. Чтобы изменить субъекта существующей сущности с помощью идентифицирующего внешнего ключа, сначала удалите зависимый объект и вызовите SaveChanges, а затем свяжите зависимость с новым субъектом.

Почему

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

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

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

Отслеживание проблемы № 24803Новые возможности: неявное владение по умолчанию

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

Как и в других поставщиках, связанные типы сущностей были обнаружены как обычные (не принадлежащие) типы.

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

Связанные типы сущностей теперь будут принадлежать типу сущности, в котором они были обнаружены. Только типы сущностей, соответствующие свойству DbSet<TEntity>, будут обнаруживаться как не принадлежащие.

Почему

Это поведение соответствует общему шаблону моделирования данных в Azure Cosmos DB — внедрение связанных данных в один документ. Azure Cosmos DB изначально не поддерживает присоединение к разным документам, поэтому моделирование связанных сущностей, так как не принадлежащие не имеют ограниченной полезности.

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

Чтобы настроить тип сущности как не принадлежащий вызовите modelBuilder.Entity<MyEntity>();

SQLite: подключения в пуле

Отслеживание проблемы № 13837Новые возможности: неявное владение по умолчанию

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

Ранее соединения в Microsoft.Data.Sqlite не были помещены в пул.

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

Начиная с 6.0 соединения помещаются в пул по умолчанию. В результате файлы базы данных поддерживаются открытыми даже после закрытия объекта подключения ADO.NET.

Почему

Объединение базовых соединений в пулы значительно повышает производительность открытия и закрытия объектов подключения ADO.NET. Это особенно заметно для сценариев, в которых открытие базового соединения требует много ресурсов, как в случае с шифрованием, или в сценариях с большим количеством кратковременных соединений с базой данных.

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

Объединение соединений в пулы можно отключить, добавив Pooling=False в строку подключения.

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

SqliteConnection.ClearPool(connection);
File.Delete(databaseFile);

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

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

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

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

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

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

Почему

Связи "многие ко многим" без явных типов соединений были введены в EF Core 5.0 и являются более лаконичным и естественным способом представления простых таблиц соединений.

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

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

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

public partial class PostTag
{
    public int PostsId { get; set; }
    public int TagsId { get; set; }

    public virtual Post Posts { get; set; }
    public virtual Tag Tags { get; set; }
}

public partial class Post
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

public partial class Tag
{
    public virtual ICollection<PostTag> PostTags { get; set; }
}

Затем добавьте конфигурацию для типа соединения и переходов в разделяемый класс для DbContext:

public partial class DailyContext
{
    partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>(entity =>
        {
            entity.HasMany(d => d.Tags)
                .WithMany(p => p.Posts)
                .UsingEntity<PostTag>(
                    l => l.HasOne<Tag>(e => e.Tags).WithMany(e => e.PostTags).HasForeignKey(e => e.TagsId),
                    r => r.HasOne<Post>(e => e.Posts).WithMany(e => e.PostTags).HasForeignKey(e => e.PostsId),
                    j =>
                    {
                        j.HasKey("PostsId", "TagsId");
                        j.ToTable("PostTag");
                    });
        });
    }
}

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

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

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

Очищено сопоставление между DeleteBehavior и значениями ON DELETE

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

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

Некоторые из сопоставлений между поведением OnDelete() связи и поведением ON DELETE внешних ключей в базе данных были несогласованны как в миграции, так и в формировании шаблонов.

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

В следующей таблице показаны изменения для миграций.

OnDelete() ON DELETE
NoAction NO ACTION
ClientNoAction NO ACTION
Ограничить RESTRICT
Cascade CASCADE
ClientCascade RESTRICT NO ACTION
SetNull SET NULL
ClientSetNull RESTRICT NO ACTION

Для формирования шаблонов произошли следующие изменения.

ON DELETE OnDelete()
NO ACTION ClientSetNull
RESTRICT ClientSetNull Restrict
CASCADE Cascade
SET NULL SetNull

Почему

Новые сопоставления являются более последовательными. Поведение базы данных по умолчанию для NO ACTION теперь является предпочтительным по сравнению с более ограничивающим и менее производительным поведением RESTRICT.

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

Поведение по умолчанию OnDelete() для необязательных связей теперь имеет значение ClientSetNull. Его сопоставление изменилось с RESTRICT на NO ACTION. Это может привести к созданию большого количества операций при первой миграции, добавленной после обновления до EF Core 6.0.

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

SQL Server не поддерживает RESTRICT, поэтому эти внешние ключи уже были созданы с помощью NO ACTION. Операции миграции не влияют на SQL Server и могут быть удалены.

База данных в памяти проверяет, что обязательные свойства не содержат значений NULL

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

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

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

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

База данных в памяти вызывает исключение Microsoft.EntityFrameworkCore.DbUpdateException, если вызывается SaveChanges или SaveChangesAsync и обязательное свойство имеет значение NULL.

Почему

Поведение базы данных в памяти теперь соответствует поведению других баз данных.

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

Предыдущее поведение (т. е. отсутствие проверки значений NULL) можно восстановить при настройке поставщика в памяти. Например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseInMemoryDatabase("MyDatabase", b => b.EnableNullChecks(false));
}

Удалено последнее предложение ORDER BY при присоединении коллекций

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

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

При выполнении присоединений SQL для коллекций (связи "один ко многим") в EF Core добавлялось предложение ORDER BY для каждого ключевого столбца присоединяемой таблицы. Например, для загрузки всех сущностей Blog со связанными сущностями Post использовался следующий код SQL:

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]

Эти упорядочения необходимы для надлежащей материализации сущностей.

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

Теперь самое последнее предложение ORDER для присоединения коллекции опускается:

SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]

Предложение ORDER BY для столбца ИД сущности Post больше не создается.

Почему

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

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

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

DbSet больше не реализует IAsyncEnumerable

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

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

DbSet<TEntity>, который применяется для выполнения запросов к DbContext, раньше реализовывал IAsyncEnumerable<T>.

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

DbSet<TEntity> больше не реализует IAsyncEnumerable<T> напрямую.

Почему

DbSet<TEntity> изначально был создан для реализации IAsyncEnumerable<T> в основном для того, чтобы разрешить его прямое перечисление посредством конструкции foreach. К сожалению, если проект также ссылался на System.Linq.Async для создания асинхронных операторов LINQ на стороне клиента, это приводило к неоднозначной ошибке вызова между операторами, определенными выше IQueryable<T>, а также определенными выше IAsyncEnumerable<T>. В C# 9 добавлена поддержка расширения GetEnumerator для циклов foreach, в результате чего был удален источник основной причины ссылки на IAsyncEnumerable.

Подавляющее большинство сценариев, где используется DbSet, продолжат работать как есть, поскольку они либо состоят из операторов LINQ выше DbSet, либо перечисляют его и т. д. Исключением являются сценарии использования, где выполняется попытка привести DbSet непосредственно к IAsyncEnumerable.

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

Если необходимо ссылаться на DbSet<TEntity> как на IAsyncEnumerable<T>, вызовите метод DbSet<TEntity>.AsAsyncEnumerable, чтобы явно привести его.

Возвращаемый тип сущности TVF также сопоставляется с таблицей по умолчанию

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

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

Тип сущности не был сопоставлен с таблицей по умолчанию при использовании в качестве возвращаемого типа TVF, настроенного с HasDbFunction.

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

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

Почему

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

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

Чтобы удалить сопоставление таблицы по умолчанию, вызовите ToTable(EntityTypeBuilder, String):

modelBuilder.Entity<MyEntity>().ToTable((string?)null));

Уникальность имени ограничения проверки теперь подтверждается

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

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

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

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

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

Почему

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

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

В некоторых случаях допустимые имена ограничений проверки могут отличаться из-за этого изменения. Чтобы явно указать нужное имя, вызовите HasName:

modelBuilder.Entity<MyEntity>().HasCheckConstraint("CK_Id", "Id > 0", c => c.HasName("CK_MyEntity_Id"));

Добавлены интерфейсы метаданных IReadOnly и удалены методы расширения

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

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

Существует три набора интерфейсов метаданных: IModel, IMutableModel и IConventionModel, а также методы расширения.

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

Добавлен новый набор интерфейсов IReadOnly, например IReadOnlyModel. Методы расширения, которые ранее были определены для интерфейсов метаданных, были преобразованы в методы интерфейса по умолчанию.

Почему

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

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

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

IExecutionStrategy теперь является одноэлементной службой

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

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

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

Почему

Это сократило выделения в двух критических путях EF.

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

Производные от ExecutionStrategy реализации должны очищать любое состояние в OnFirstExecution().

Условную логику в делегате, переданном в ExecutionStrategy, необходимо переместить в пользовательскую реализацию IExecutionStrategy.

SQL Server: дополнительные ошибки считаются временными

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

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

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

Почему

Мы продолжаем собирать у пользователей и команды SQL Server отзывы о том, какие ошибки должны считаться временными.

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

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

Azure Cosmos DB: дополнительные символы экранируются в значениях id

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

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

В EF Core 5 только '|' экранировались в значениях id.

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

В EF Core 6 '/', '\', '?' и '#' также экранировались в значениях id.

Почему

Эти символы недопустимы, как описано в Resource.Id. Их использование в id приведет к сбою запросов.

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

Созданное значение можно переопределить, задав его перед тем, как сущность будет помечена как Added:

var entry = context.Attach(entity);
entry.Property("__id").CurrentValue = "MyEntity|/\\?#";
entry.State = EntityState.Added;

Некоторые одноэлементные службы теперь находятся в области

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

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

Многие службы запросов и некоторые службы времени разработки, зарегистрированные как Singleton, теперь зарегистрированы как Scoped.

Почему

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

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

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

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

Новый API кэширования для расширений, которые добавляют или заменяют службы

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

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

В EF Core 5 GetServiceProviderHashCode возвращал long и использовался напрямую как часть ключа кэша для поставщика услуг.

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

GetServiceProviderHashCode теперь возвращает int и используется только для вычисления хэш-кода ключа кэша для поставщика услуг.

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

Почему

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

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

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

private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
    public ExtensionInfo(IDbContextOptionsExtension extension)
        : base(extension)
    {
    }

    ...

    public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
        => other is ExtensionInfo;
}

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

Новая процедура инициализации модели моментального снимка и времени разработки

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

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

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

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

Внедрение IModelRuntimeInitializer призвано скрыть некоторые из необходимых шагов, а модель времени выполнения, которая не содержит все метаданные миграции, позволит использовать модель времени разработки для фиксации разницы между моделями.

Почему

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

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

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

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

var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;

if (snapshotModel is IMutableModel mutableModel)
{
    snapshotModel = mutableModel.FinalizeModel();
}

if (snapshotModel != null)
{
    snapshotModel = context.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
}

var hasDifferences = context.GetService<IMigrationsModelDiffer>().HasDifferences(
    snapshotModel?.GetRelationalModel(),
    context.GetService<IDesignTimeModel>().Model.GetRelationalModel());

В этом фрагменте показано, как реализовать IDesignTimeDbContextFactory<TContext> с помощью создания модели извне и вызова UseModel:

internal class MyDesignContext : IDesignTimeDbContextFactory<MyContext>
{
    public TestContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder();
        optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DB"));

        var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder();
        CustomizeModel(modelBuilder);
        var model = modelBuilder.Model.FinalizeModel();

        var serviceContext = new MyContext(optionsBuilder.Options);
        model = serviceContext.GetService<IModelRuntimeInitializer>().Initialize(model);
        return new MyContext(optionsBuilder.Options);
    }
}

OwnedNavigationBuilder.HasIndex теперь возвращает другой тип

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

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

В EF Core 5 HasIndex возвращал IndexBuilder<TEntity>, где TEntity — тип владельца.

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

HasIndex теперь возвращает IndexBuilder<TDependentEntity>, где TDependentEntity — принадлежащий тип.

Почему

Возвращенный объект построителя был некорректно типизирован.

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

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

DbFunctionBuilder.HasSchema(null) переопределяет [DbFunction(Schema = "schema")]

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

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

В EF Core 5 при вызове HasSchema со значением null не сохранялся источник конфигурации, поэтому DbFunctionAttribute мог переопределить его.

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

Теперь при вызове HasSchema со значением null сохраняется источник конфигурации и предотвращается его переопределение атрибутом.

Почему

Конфигурация, указанная с помощью API ModelBuilder, не должна быть доступна для переопределения заметками к данным.

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

Удалите вызов HasSchema, чтобы атрибут мог настроить схему.

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

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

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

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

public class Foo
{
    public int Id { get; set; }

    public Bar Bar { get; set; } = new(); // Don't do this.
}

public class Bar
{
    public int Id { get; set; }
}

Запрос без отслеживания для Foo, включающего Bar, задавал Foo.Bar для сущности, запрашиваемой из базы данных. Например, этот код:

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Выводил Foo.Bar.Id = 1.

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

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Выводил Foo.Bar.Id = 0.

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

В EF Core 6.0 поведение запросов с отслеживанием теперь совпадает с поведением запросов без отслеживания. Это означает, что этот код:

var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

И этот код:

var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");

Выводят Foo.Bar.Id = 1.

Почему

У этого изменения две причины:

  1. Чтобы обеспечить одинаковое поведение запросов с отслеживанием и без.
  2. При запросе базы данных разумно предположить, что код приложения хочет вернуть значения, хранящиеся в базе данных.

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

Существует два решения:

  1. Не запрашивать из базы данных объекты, которые не должны включаться в результаты. Например, в приведенных выше фрагментах кода не Include Foo.Bar Bar следует возвращать экземпляр из базы данных и включать их в результаты.
  2. Задайте значение навигации после выполнения запроса из базы данных. Например, в приведенных выше фрагментах кода вызовите foo.Bar = new() после выполнения запроса.

Кроме того, рекомендуется не инициализировать связанные экземпляры сущностей как объекты по умолчанию. Это означает, что связанный экземпляр — это новая сущность, не сохраненная в базе данных, без набора значений ключа. Если вместо этого связанная сущность существует в базе данных, то данные в коде конфликтуют с данными, хранящимися в базе данных.

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

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

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

Свойства перечисления могут быть сопоставлены со строковыми столбцами в базе данных с помощью HasConversion<string>() или EnumToStringConverter. При этом EF Core преобразует строковые значения в столбце в соответствующие элементы типа перечисления .NET. Однако если строковое значение не совпадало с элементом перечисления, свойство устанавливалось в значение по умолчанию для перечисления.

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

EF Core 6.0 теперь создает исключение InvalidOperationException с сообщением: "Не удается преобразовать строковое значение {value} из базы данных в любое значение в сопоставленном перечислении {enumType}".

Почему

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

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

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

DbFunctionBuilder.HasTranslation теперь предоставляет аргументы функции как IReadOnlyList, а не IReadOnlyCollection

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

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

При настройке преобразования для определяемой пользователем функции с помощью метода HasTranslation аргументы функции были предоставлены как IReadOnlyCollection<SqlExpression>.

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

В EF Core 6.0 аргументы теперь предоставляются в виде IReadOnlyList<SqlExpression>.

Почему

IReadOnlyList позволяет использовать индексаторы, поэтому доступ к аргументам упростился.

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

Нет. IReadOnlyList реализует интерфейс IReadOnlyCollection, поэтому переход должен быть простым.

Сопоставление таблиц по умолчанию не удаляется, если сущность сопоставлена с функцией с табличным значением

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

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

Если сущность была сопоставлена с функцией с табличным значением, ее сопоставление по умолчанию с таблицей удалялось.

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

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

Почему

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

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

Сопоставление с таблицей можно явно отключить в конфигурации модели:

modelBuilder.Entity<MyEntity>().ToTable((string)null);

целевые объекты dotnet-ef .NET 6

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

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

Целевая платформа .NET Core 3.1 уже выбрана с помощью команды dotnet-ef некоторое время назад. Это позволило использовать более новую версию средства без установки более новых версий среды выполнения .NET.

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

Средством dotnet-ef в EF Core 6.0.6 в качестве целевой платформы теперь выбрана .NET 6. Вы по-прежнему можете использовать средство для проектов, предназначенных для более старых версий .NET и .NET Core, но для запуска этого средства необходимо установить среду выполнения .NET 6.

Почему

Пакет SDK .NET 6.0.200 обновил поведение dotnet tool install на osx-arm64, чтобы создать оболочку osx-x64 для инструментов, предназначенных для .NET Core 3.1. Чтобы поддерживать рабочий интерфейс по умолчанию для dotnet-ef, нам пришлось обновить его, предназначив для платформы .NET 6.

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

Чтобы запустить dotnet-ef без установки среды выполнения .NET 6, можно установить более раннюю версию средства:

dotnet tool install dotnet-ef --version 3.1.*

IModelCacheKeyFactory Для обработки кэширования во время разработки может потребоваться обновить реализации.

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

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

IModelCacheKeyFactory Не имеет возможности кэшировать модель времени разработки отдельно от модели среды выполнения.

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

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

System.InvalidOperationException: "Запрошенная конфигурация не хранится в оптимизированной для чтения модели, используйте dbContext.GetService<IDesignTimeModel>(). Модель'.

Почему

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

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

Реализуйте новую перегрузку. Например:

public object Create(DbContext context, bool designTime)
    => context is DynamicContext dynamicContext
        ? (context.GetType(), dynamicContext.UseIntProperty, designTime)
        : (object)context.GetType();

Навигация "{navigation}" была проигнорирована из "Include" в запросе, так как исправление автоматически заполняет его. Если последующие навигации указаны в параметре "Включить", они будут игнорироваться. Вернуться обратно в дерево включения не допускается.

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

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

Событие CoreEventId.NavigationBaseIncludeIgnored было зарегистрировано как предупреждение по умолчанию.

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

Событие CoreEventId.NavigationBaseIncludeIgnored было зарегистрировано как ошибка по умолчанию и приводит к возникновению исключения.

Почему

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

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

Старое поведение можно восстановить, настроив событие как предупреждение. Например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Warn(CoreEventId.NavigationBaseIncludeIgnored));