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

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

Итоги

Критические изменения Воздействие
EF Core 5.0 не поддерживает платформу .NET Framework Средняя
IProperty.GetColumnName() считается устаревшим Средняя
Для десятичных значений требуется указывать значения точности и масштаба Средняя
Атрибут обязательного поля или поля, не допускающего значения NULL, имеет разную семантику для навигации к основной сущности и навигации к зависимой сущности Средняя
Определение запроса заменено методами, зависящими от поставщика Средняя
Навигации по ссылке, которая не является пустой, не переопределяются запросами Средняя
ToView() по-разному обрабатывается миграциями Средняя
ToTable(null) помечает тип сущности как не сопоставленный с таблицей Средняя
Из расширения NTS для SQLite удален метод HasGeometricDimension. Низкая
Azure Cosmos DB: ключ секции теперь добавляется в первичный ключ Низкая
Azure Cosmos DB: id свойство переименовано в __id Низкая
Azure Cosmos DB: байт[] теперь хранится в виде строки base64 вместо массива чисел Низкая
Azure Cosmos DB: GetPropertyName и SetPropertyName были переименованы Низкая
Генераторы значений вызываются, когда состояние сущности меняется с "отсоединено" на "не изменено", "обновлено" или "удалено" Низкая
IMigrationsModelDiffer теперь использует IRelationalModel Низкая
Дискриминаторы доступны только для чтения Низкая
Относящиеся к поставщику методы EF.Functions вызываются для поставщика InMemory Низкая
IndexBuilder.HasName считается устаревшим Низкая
Теперь плюрайзер включен для шаблонов реверсивных моделей Низкая
INavigationBase заменяет INavigation в некоторых API, чтобы поддерживать пропуск навигации Низкая
Некоторые запросы с помощью коррелированной коллекции, которые также используют Distinct или GroupBy, больше не поддерживаются. Низкая
Использование коллекции запрашиваемого типа в проекции не поддерживается Низкая

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

EF Core 5.0 не поддерживает платформу .NET Framework

Отслеживание вопроса № 15498

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

EF Core 3.1 предназначается для платформы .NET Standard 2.0, которая поддерживается платформой .NET Framework.

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

EF Core 5.0 предназначается для платформы .NET Standard 2.1, которая не поддерживается платформой .NET Framework. Это означает, что EF Core 5.0 нельзя использовать с приложениями .NET Framework.

Причина

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

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

В приложениях .NET Framework по-прежнему возможно использование выпуска EF Core 3.1, который рассчитан на долгосрочную поддержку (LTS). Кроме того, можно обновить приложения для работы с .NET Core 3.1 и (или) .NET Core 5, которые поддерживают .NET Standard 2.1.

IProperty.GetColumnName() считается устаревшим

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

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

GetColumnName() возвращал имя столбца, с которым сопоставлено свойство.

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

GetColumnName() по-прежнему возвращает имя столбца, с которым сопоставлено свойство, но это поведение стало неоднозначным, так как EF Core 5 поддерживает TPT и одновременное сопоставление с представлением или функцией, где эти сопоставления могут использовать разные имена столбцов для одного и того же свойства.

Причина

Мы пометили этот метод как устаревший, чтобы помочь пользователям более точно перегружать GetColumnName(IProperty, StoreObjectIdentifier).

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

Если тип сущности сопоставляется только с одной таблицей и никогда не сопоставляется с представлениями, функциями или несколькими таблицами, GetColumnBaseName(IReadOnlyProperty) можно использовать в EF Core 5.0 и 6.0 для получения имени таблицы. Например:

var columnName = property.GetColumnBaseName();

В EF Core 7.0 это снова можно заменить новым GetColumnName, который ведет себя так же, как исходный вариант для простых, только сопоставлений отдельных таблиц.

Если тип сущности может быть сопоставлен с представлениями, функциями или несколькими таблицами, StoreObjectIdentifier необходимо получить для идентификации таблицы, представления или функции. Затем его можно использовать для получения имени столбца для этого объекта хранилища. Например:

var columnName = property.GetColumnName(StoreObjectIdentifier.Table("Users", null)));

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

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

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

В EF Core, как правило, не устанавливались значения точности и масштаба для объектов SqlParameter. Это означает, что полные значения точности и масштаба отправлялись в SQL Server, где на этом этапе выполнялось округление в зависимости от значений точности и масштаба, заданных для столбца базы данных.

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

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

Причина

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

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

Сопоставьте свойства десятичного значения с использованием имени типа, которое включает в себя значения точности и масштаба. Например:

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

    [Column(TypeName = "decimal(16, 5)")]
    public decimal Score { get; set; }
}

Также можно использовать HasPrecision в API построения модели. Например:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().Property(e => e.Score).HasPrecision(16, 5);
    }

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

Отслеживание вопроса № 17286

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

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

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

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

Вызов IsRequired перед указанием зависимого элемента теперь неоднозначен.

modelBuilder.Entity<Blog>()
    .HasOne(b => b.BlogImage)
    .WithOne(i => i.Blog)
    .IsRequired()
    .HasForeignKey<BlogImage>(b => b.BlogForeignKey);

Причина

Необходимо новое поведение для обеспечения поддержки требуемых зависимых объектов (см. #12100).

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

Удалите атрибут RequiredAttribute из навигации в зависимый объект и поместите его вместо этого в навигацию к основному объекту или настройте связь в OnModelCreating.

modelBuilder.Entity<Blog>()
    .HasOne(b => b.BlogImage)
    .WithOne(i => i.Blog)
    .HasForeignKey<BlogImage>(b => b.BlogForeignKey)
    .IsRequired();

Определение запроса заменено методами, зависящими от поставщика

Отслеживание вопроса № 18903

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

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

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

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

Причина

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

  • Если определяющий запрос проецировал тип сущности с помощью new { ... } в методе Select, определение его как сущности требовало дополнительной работы и не согласовывалось с тем, как номинальные типы в запросе обрабатываются в EF Core.
  • Для реляционных поставщиков FromSql по-прежнему требуется для передачи строки SQL в виде выражения LINQ.

Изначально определяющие запросы появились как представления на стороне клиента, предназначенные для использования с поставщиком в памяти для сущностей без ключей (аналогично представлениям базы данных в реляционных базах данных). Такое определение упрощает тестирование приложения с базой данных в памяти. Впоследствии они стали применяться более широко. Это было полезной возможностью, но вносило несогласованность и затрудняло понимание. Поэтому мы решили упростить принцип их работы. Теперь определяющий запрос на основе LINQ предназначен исключительно для поставщика в памяти и обрабатывается по-особому. Дополнительные сведения см. в этой проблеме.

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

Для реляционных поставщиков используйте метод ToSqlQuery в OnModelCreating и передайте строку SQL, которая должна использоваться для типа сущности. Для поставщика в памяти используйте метод ToInMemoryQuery в OnModelCreating и передайте запрос SQL, который должен использоваться для типа сущности.

Навигации по ссылке, которая не является пустой, не переопределяются запросами

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

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

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

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

Начиная с версии EF Core 5.0, навигации по пустой ссылке, которая не является пустой, никогда не перезаписываются экземплярами, возвращаемыми запросом.

Обратите внимание, что безотложная инициализация навигации по коллекции для пустой коллекции по-прежнему поддерживается.

Причина

Инициализация свойства навигации по ссылкам для "пустого" экземпляра сущности приводит к неоднозначному состоянию. Например:

public class Blog
{
     public int Id { get; set; }
     public Author Author { get; set; ) = new Author();
}

Обычно запрос блогов и авторов сначала создает экземпляры Blog, а затем задает соответствующие экземпляры Author на основе данных, возвращаемых из базы данных. Однако в этом случае каждое свойство Blog.Author будет уже инициализировано для пустого свойства Author. Кроме EF Core, не существует способа выяснить, является ли этот экземпляр "пустым". Таким образом, перезапись этого экземпляра может без уведомления привести к выводу допустимого Author. Таким образом, EF Core 5.0 теперь не перезаписывает уже инициализированную навигацию.

Это новое поведение также соответствует обычному поведению EF6, хотя при исследовании мы также обнаружили некоторые случаи несогласованности в EF6.

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

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

ToView() по-разному обрабатывается миграциями

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

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

Вызов ToView(string) заставляет миграции игнорировать тип сущности в дополнение к сопоставлению его с представлением.

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

Теперь ToView(string) помечает тип сущности как не сопоставленный с таблицей в дополнение к сопоставлению его с представлением. Это приводит к тому, что при первой миграции после обновления до EF Core 5 предпринимается попытка удаления таблицы по умолчанию для этого типа сущности, так как он больше не игнорируется.

Причина

EF Core теперь позволяет сопоставлять тип сущности и с таблицей, и с представлением одновременно, поэтому ToView больше нельзя использовать для указания, что тип сущности следует игнорировать при миграции.

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

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

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().ToTable("UserView", t => t.ExcludeFromMigrations());
}

ToTable(null) помечает тип сущности как не сопоставленный с таблицей

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

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

ToTable(null) сбрасывает имя таблицы в значение по умолчанию.

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

ToTable(null) теперь помечает тип сущности как не сопоставленный с какой-либо таблицей.

Причина

EF Core теперь позволяет сопоставлять тип сущности и с таблицей, и с представлением одновременно, поэтому ToTable(null) используется для указания, что тип сущности не сопоставлен ни с какой таблицей.

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

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

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().Metadata.RemoveAnnotation(RelationalAnnotationNames.TableName);
}

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

Из расширения NTS для SQLite удален метод HasGeometricDimension.

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

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

Метод HasGeometricDimension использовался для включения дополнительных измерений (Z и M) для столбцов геометрии. Однако он влиял только на создание базы данных. Его не требовалось указывать для запроса значений с дополнительными измерениями. Он также не работал корректно при вставке или обновлении значений с дополнительными измерениями (см. проблему #14257).

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

Чтобы включить вставку и обновление геометрических значений с дополнительными измерениями (Z и M), необходимо указать измерение в составе имени типа столбца. Этот интерфейс API более точно соответствует базовой работе функции AddGeometryColumn расширения SpatiaLite.

Причина

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

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

Чтобы указать измерение, используйте HasColumnType:

modelBuilder.Entity<GeoEntity>(
    x =>
    {
        // Allow any GEOMETRY value with optional Z and M values
        x.Property(e => e.Geometry).HasColumnType("GEOMETRYZM");

        // Allow only POINT values with an optional Z value
        x.Property(e => e.Point).HasColumnType("POINTZ");
    });

Azure Cosmos DB: ключ секции теперь добавляется в первичный ключ

Отслеживание вопроса № 15289

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

Свойство ключа секции добавлялось только в альтернативный ключ, включающий id.

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

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

Причина

Это изменение делает модель более согласованной с семантикой Azure Cosmos DB и повышает производительность Find и некоторых запросов.

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

Чтобы предотвратить добавление свойства ключа секции в первичный ключ, настройте его в OnModelCreating.

modelBuilder.Entity<Blog>()
    .HasKey(b => b.Id);

Azure Cosmos DB: id свойство переименовано в __id

Отслеживание вопроса № 17751

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

Теневое свойство, сопоставленное со свойством JSON id, также называлось id.

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

Теневое свойство, создаваемое в соответствии с соглашением, теперь называется __id.

Причина

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

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

Чтобы вернуться к поведению, принятому в версии 3.x, настройте свойство id в OnModelCreating.

modelBuilder.Entity<Blog>()
    .Property<string>("id")
    .ToJsonProperty("id");

Azure Cosmos DB: байт[] теперь хранится в виде строки base64 вместо массива чисел

Отслеживание вопроса № 17306

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

Свойства типа byte[] хранились в виде числового массива.

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

Свойства типа byte[] теперь хранятся в виде строки base64.

Причина

Такое представление byte[] ближе к ожидаемому и используется по умолчанию в основных библиотеках сериализации JSON.

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

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

Azure Cosmos DB: GetPropertyName и SetPropertyName были переименованы

Отслеживание вопроса № 17874

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

Ранее методы расширения назывались GetPropertyName и SetPropertyName.

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

Старый интерфейс API был удален, и были добавлены новые методы: GetJsonPropertyName и SetJsonPropertyName.

Причина

Данное изменение устраняет неоднозначность в отношении того, что настраивают эти методы.

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

Используйте новый API.

Генераторы значений вызываются, когда состояние сущности меняется с "отсоединено" на "не изменено", "обновлено" или "удалено"

Отслеживание вопроса № 15289

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

Генераторы значений вызывались только при изменении состояния сущности на «добавлено».

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

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

Причина

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

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

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

IMigrationsModelDiffer теперь использует IRelationalModel

Отслеживание вопроса № 20305

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

API IMigrationsModelDiffer определялся с помощью IModel.

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

Теперь API IMigrationsModelDiffer использует IRelationalModel. Однако моментальный снимок модели по-прежнему содержит только IModel, так как этот код является частью приложения, и Entity Framework не может изменить его без внесения значительных изменений.

Причина

IRelationalModel — это вновь добавленное представление схемы базы данных. Его использование для поиска различий выполняется быстрее и точнее.

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

Используйте следующий код для сравнения модели из snapshot с моделью из context.

var dependencies = context.GetService<ProviderConventionSetBuilderDependencies>();
var relationalDependencies = context.GetService<RelationalConventionSetBuilderDependencies>();

var typeMappingConvention = new TypeMappingConvention(dependencies);
typeMappingConvention.ProcessModelFinalizing(((IConventionModel)modelSnapshot.Model).Builder, null);

var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies);
var sourceModel = relationalModelConvention.ProcessModelFinalized(snapshot.Model);

var modelDiffer = context.GetService<IMigrationsModelDiffer>();
var hasDifferences = modelDiffer.HasDifferences(
    ((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel(),
    context.Model.GetRelationalModel());

Мы планируем улучшить этот процесс в версии 6.0 (см. #22031).

Дискриминаторы доступны только для чтения

Отслеживание вопроса № 21154

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

Можно было изменить значение дискриминатора перед вызовом метода SaveChanges

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

В приведенном выше случае будет вызвано исключение.

Причина

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

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

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

modelBuilder.Entity<BaseEntity>()
    .Property<string>("Discriminator")
    .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);

Относящиеся к поставщику методы EF.Functions вызываются для поставщика InMemory

Отслеживание вопроса № 20294

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

Относящиеся к конкретному поставщику методы EF.Functions содержали реализацию для выполнения на стороне клиента, что позволяло им выполняться в поставщике InMemory. Например, EF.Functions.DateDiffDay — это относящийся к SQL Server метод, который работал в поставщике InMemory.

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

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

Причина

Методы, относящиеся к конкретному поставщику, сопоставляются с функцией базы данных. Вычисление, выполняемое сопоставленной функцией базы данных, не всегда может реплицироваться на стороне клиента в LINQ. Поэтому результаты, полученные при выполнении одного и того же метода на сервере и в клиенте, могут различаться. Так как эти методы используются в LINQ для преобразования в определенные функции базы данных, их не нужно вычислять на стороне клиента. Так как поставщик InMemory — это другая база данных, эти методы недоступны для него. При попытке выполнить их для поставщика InMemory или любого другого поставщика, который не преобразует эти методы, создается исключение.

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

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

IndexBuilder.HasName считается устаревшим

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

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

Ранее допускалось определение только одного индекса для конкретного набора свойств. Имя для базы данных индекса настраивалось с использованием IndexBuilder.HasName.

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

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

Причина

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

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

Любой код, который ранее вызывал IndexBuilder.HasName, необходимо изменить для вызова HasDatabaseName.

Если в проекте есть миграции, созданные до версии EF Core 2.0.0, вы можете игнорировать предупреждения для этих файлов, подавив их с помощью #pragma warning disable 612, 618.

Добавлено средство преобразования во множественное число для формирования шаблонов реконструированных моделей

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

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

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

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

Теперь EF Core содержит собственное средство для преобразования во множественное число на основе библиотеки Humanizer. Именно эту библиотеку использует Visual Studio, рекомендуя имена переменных.

Причина

Использование форм множественного числа для свойств коллекций и единственного числа для типов и ссылочных свойств считается идиоматичным в .NET.

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

Чтобы отключить средство для преобразования во множественное число, укажите параметр --no-pluralize для dotnet ef dbcontext scaffold или -NoPluralize для Scaffold-DbContext.

INavigationBase заменяет INavigation в некоторых API, чтобы поддерживать пропуск навигации

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

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

В версиях EF Core, предшествующих версии 5.0, поддерживалась только одна форма свойства навигации, представленная интерфейсом INavigation.

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

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

Причина

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

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

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

Некоторые запросы с помощью коррелированной коллекции, которые также используют Distinct или GroupBy, больше не поддерживаются.

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

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

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

Пример GroupBy:

context.Parents
    .Select(p => p.Children
        .GroupBy(c => c.School)
        .Select(g => g.Key))

Пример Distinct, в частности запросы Distinct, в которых внутренняя проекция коллекции не содержит первичный ключ:

context.Parents
    .Select(p => p.Children
        .Select(c => c.School)
        .Distinct())

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

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

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

Причина

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

Устранение рисков

Перепишите запрос, чтобы не использовать операции GroupBy или Distinct во внутренней коллекции, и выполните эти операции на клиенте.

context.Parents
    .Select(p => p.Children.Select(c => c.School))
    .ToList()
    .Select(x => x.GroupBy(c => c).Select(g => g.Key))
context.Parents
    .Select(p => p.Children.Select(c => c.School))
    .ToList()
    .Select(x => x.Distinct())

Использование коллекции запрашиваемого типа в проекции не поддерживается

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

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

Ранее в отдельных случаях коллекцию запрашиваемых типов можно было использовать в проекции, например как аргумент для конструктора List<T>:

context.Blogs
    .Select(b => new List<Post>(context.Posts.Where(p => p.BlogId == b.Id)))

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

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

Причина

Мы не можем реализовать объект запрашиваемого типа, поэтому такие объекты автоматически создавались с помощью типа List<T>. Это часто приводило к выводу исключения из-за несоответствия типов, которое было непонятным и могло быть неожиданным для некоторых пользователей. Мы решили распознать шаблон и выдать более осмысленное исключение.

Устранение рисков

Добавьте вызов ToList() после запрашиваемого объекта в проекции:

context.Blogs.Select(b => context.Posts.Where(p => p.BlogId == b.Id).ToList())