Критические изменения в EF Core 3.x

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

Сводка

Критическое изменение Влияние
Запросы LINQ больше не вычисляются на клиенте Высокий
Средство командной строки EF Core, dotnet ef больше не входит в пакет SDK для .NET Core Высокий
DetectChanges учитывает значения ключей, сформированные хранилищем Высокий
FromSql, ExecuteSql и ExecuteSqlAsync были переименованы Высокий
Типы запросов объединяются с типами сущностей Высокий
Entity Framework Core больше не является частью общей платформы ASP.NET Core Средний
Каскадные удаления теперь по умолчанию выполняются немедленно Средний
Безотложная загрузка связанных сущностей теперь происходит в одном запросе Средний
Более четкая семантика DeleteBehavior.Restrict Средний
Изменение API конфигурации для отношений типа принадлежности Средний
Каждое свойство использует создание независимых целочисленных ключей в памяти Средний
Запросы без отслеживания больше не выполняют разрешение идентификаторов Средний
Изменения API метаданных Средний
Изменения API метаданных с учетом поставщика Средний
Удален метод UseRowNumberForPaging Средний
Метод FromSql не поддерживает составление при использовании с хранимой процедурой Средний
Методы FromSql можно указать только в корневых элементах запроса Низкий
Временные значения ключа больше не устанавливаются для экземпляров сущностей Низкий
Зависимые сущности, имеющие общую с субъектом таблицу, теперь являются необязательными Низкий
Все сущности, имеющие общую таблицу со столбцом маркера параллелизма, должны сопоставлять ее со свойством Низкий
Собственные сущности нельзя запрашивать без использования запроса отслеживания владельцем Низкий
Наследуемые свойства из несопоставленных типов теперь сопоставляются с одним столбцом для всех производных типов Низкий
Соглашение для свойства внешнего ключа больше не совпадает со свойством субъекта по имени Низкий
Соединение с базой данных теперь закрывается, если оно больше не используется до завершения TransactionScope Низкий
По умолчанию используются резервные поля Низкий
Исключение при обнаружении нескольких совместимых резервных полей Низкий
Имена свойств, доступных только для полей, должны совпадать с именем поля Низкий
AddDbContext/AddDbContextPool больше не вызывает метод AddLogging и AddMemoryCache Низкий
AddEntityFramework* добавляет IMemoryCache с ограниченным размером Низкий
DbContext.Entry теперь выполняет локальную процедуру DetectChanges Низкий
Ключи массива строк и байтов не формируются клиентом по умолчанию Низкий
ILoggerFactory теперь является службой с ограниченной областью действия Низкий
Прокси с отложенной загрузкой больше не предполагают полную загрузку свойств навигации Низкий
Чрезмерное создание внутренних поставщиков служб теперь является ошибкой по умолчанию Низкий
Новое поведение для вызова HasOne/HasMany с одной строкой Низкий
Тип возвращаемого значения для нескольких асинхронных методов изменен с Task на ValueTask Низкий
Заметка Relational:TypeMapping теперь является просто TypeMapping Низкий
ToTable для производного типа выдает исключение Низкий
EF Core больше не отправляет pragma для принудительного применения FK SQLite Низкий
Теперь Microsoft.EntityFrameworkCore.Sqlite зависит от SQLitePCLRaw.bundle_e_sqlite3 Низкий
Теперь значения Guid хранятся в SQLite в виде значений типа TEXT Низкий
Теперь значения типа Char хранятся в SQLite в виде значений типа TEXT Низкий
Идентификаторы миграции теперь создаются с использованием календаря инвариантного языка и региональных параметров Низкий
Сведения о расширении и его метаданные были удалены из интерфейса IDbContextOptionsExtension Низкий
Оператор LogQueryPossibleExceptionWithAggregateOperator был переименован Низкий
Уточнение API для имен ограничений внешнего ключа Низкий
Методы IRelationalDatabaseCreator.HasTables/HasTablesAsync стали общедоступными Низкий
Microsoft.EntityFrameworkCore.Design теперь является пакетом DevelopmentDependency Низкий
Библиотека SQLitePCL.raw обновлена до версии 2.0.0 Низкий
Обновление NetTopologySuite до версии 2.0.0 Низкий
Вместо System.Data.SqlClient используется Microsoft.Data.SqlClient Низкий
Множество неоднозначных связей со ссылкой на себя теперь требуют настройки Низкий
DbFunction.Schema в виде нулевой или пустой строки выполняет настройку для включения в схему по умолчанию для модели Низкий
Версия EF Core 3.0 больше предназначена для .NET Standard 2.1, а не для .NET Standard 2.0 отменено
Выполнение запроса с ведением журнала на уровне отладки отменено

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

Запросы LINQ больше не вычисляются на клиенте

Отслеживание вопроса № 14935Также см. вопрос № 12795

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

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

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

Начиная с версии 3.0 система EF Core разрешает вычислять на клиенте только выражения в высокоуровневой проекции (последний вызов Select() в запросе). Если выражения в любой другой части запроса невозможно преобразовать в код SQL или параметр, возникает исключение.

Почему

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

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

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

Если запрос невозможно преобразовать полностью, то перепишите его в форме, которую можно преобразовать, либо используйте AsEnumerable(), ToList() или аналогичный элемент, чтобы явно перенести данные обратно на клиент, где можно произвести их дальнейшую обработку с помощью LINQ-to-Objects.

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

Entity Framework Core больше не является частью общей платформы ASP.NET Core

Отслеживание объявлений о вопросе № 325

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

До выхода ASP.NET Core 3.0 при добавлении ссылки на пакет для Microsoft.AspNetCore.App или Microsoft.AspNetCore.All она включала в себя EF Core и некоторые поставщики данных EF Core, например поставщик SQL Server.

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

Начиная с версии 3.0 общая платформа ASP.NET Core не включает в себя EF Core или поставщики данных EF Core.

Почему

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

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

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

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

Средство командной строки EF Core, dotnet ef больше не входит в пакет SDK для .NET Core

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

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

До версии 3.0 средство dotnet ef входило в состав пакета SDK для .NET Core и было доступно для использования из командной строки любого проекта без дополнительных действий.

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

Начиная с версии 3.0 средство dotnet ef не входит в состав пакета SDK для .NET, поэтому если вам нужно его использовать, явно установите его как локальное или глобальное средство.

Почему

Это изменение позволяет распространять и обновлять dotnet ef как обычное средство .NET CLI в NuGet в соответствии с тем, что EF Core 3.0 также всегда распространяется в виде пакета NuGet.

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

Чтобы управлять миграциями или сформировать шаблон DbContext, установите dotnet-ef как глобальное средство.

dotnet tool install --global dotnet-ef

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

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

FromSql, ExecuteSql и ExecuteSqlAsync были переименованы

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

Важно!

ExecuteSqlCommand и ExecuteSqlCommandAsync являются устаревшими. Используйте эти методы вместо них.

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

До выхода EF Core 3.0 имена этих методов были перегружены для работы с обычной строкой или строкой, которая должна быть интерполирована в SQL и параметры.

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

Начиная с EF Core 3.0 используйте FromSqlRaw, ExecuteSqlRaw и ExecuteSqlRawAsync для создания параметризованного запроса, где параметры передаются отдельно из строки запроса. Пример:

context.Products.FromSqlRaw(
    "SELECT * FROM Products WHERE Name = {0}",
    product.Name);

Используйте FromSqlInterpolated, ExecuteSqlInterpolated и ExecuteSqlInterpolatedAsync для создания параметризованного запроса, где параметры передаются как часть интерполированной строки запроса. Пример:

context.Products.FromSqlInterpolated(
    $"SELECT * FROM Products WHERE Name = {product.Name}");

Обратите внимание, что оба упомянутых выше запроса будут возвращать одинаковый параметризованный SQL с одинаковыми параметрами SQL.

Почему

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

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

Перейдите на использование новых имен методов.

Метод FromSql не поддерживает составление при использовании с хранимой процедурой

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

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

До версии EF Core 3.0 метод FromSql пытался определить, можно ли выполнить составление для переданного SQL-запроса. Если SQL-запрос не допускал составления, как в случае с хранимой процедурой, вычисление производилось на стороне клиента. В случае с приведенным ниже запросом хранимая процедура выполнялась на сервере, а метод FirstOrDefault — на стороне клиента.

context.Products.FromSqlRaw("[dbo].[Ten Most Expensive Products]").FirstOrDefault();

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

Начиная с версии 3.0 платформа EF Core не пытается анализировать код SQL. Поэтому если вы выполняете составление после метода FromSqlRaw или FromSqlInterpolated, EF Core составляет SQL-запрос, вызывая вложенный запрос. При использовании хранимой процедуры с составлением вы получите исключение, вызванное недопустимым синтаксисом SQL.

Почему

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

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

Если вы используете хранимую процедуру в методе FromSqlRaw или FromSqlInterpolated, то знаете, что составление для нее невозможно, поэтому вы можете добавить AsEnumerable или AsAsyncEnumerable сразу после вызова метода FromSql, чтобы избежать составления на стороне сервера.

context.Products.FromSqlRaw("[dbo].[Ten Most Expensive Products]").AsEnumerable().FirstOrDefault();

Методы FromSql можно указать только в корневых элементах запроса.

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

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

До EF Core версии 3.0 метод FromSql можно было определять в любом месте в запросе.

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

Начиная с EF Core версии 3.0, новые методы FromSqlRaw и FromSqlInterpolated (которые заменяют FromSql) можно указывать только в корневых элементах запроса, т. е. непосредственно в DbSet<>. Попытка указать их в любом другом месте приведет к ошибке компиляции.

Почему

Определение FromSql в любом месте за пределами DbSet не предоставляет никаких преимуществ и может привести к неоднозначности в некоторых сценариях.

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

Вызовы FromSql нужно поместить непосредственно в элемент DbSet, к которому они применяются.

Запросы без отслеживания больше не выполняют разрешение идентификаторов

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

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

До EF Core 3.0 один и тот же экземпляр сущности будет использоваться для каждого вхождения сущности с заданным типом и ИД. Это соответствует поведению запросов отслеживания. Например, следующий запрос:

var results = context.Products.Include(e => e.Category).AsNoTracking().ToList();

возвращает тот же экземпляр Category для каждого Product, связанного с заданной категорией.

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

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

Почему

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

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

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

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

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

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

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

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

Начиная с версии 3.0 EF Core сохраняет временное значение ключа как часть сведений об отслеживании сущности, оставляя само свойство ключа без изменений.

Почему

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

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

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

  • Отказ от использования ключей, сформированных хранилищем.
  • Настройка свойств навигации для формирования отношений вместо задания значений внешнего ключа.
  • Получение фактических временных значений ключа из сведений об отслеживании сущности. Например, context.Entry(blog).Property(e => e.Id).CurrentValue возвратит временное значение, даже если сам blog.Id не был задан.

DetectChanges учитывает значения ключей, сформированные хранилищем

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

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

До выпуска EF Core 3.0 неотслеживаемая сущность, обнаруженная DetectChanges, отслеживалась в состоянии Added и вставлялась в новую строку при вызове SaveChanges.

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

Начиная с EF Core 3.0, если сущность использует сформированные значения ключа и какое-либо значение ключа задано, то она будет отслеживаться в состоянии Modified. Это означает, что предполагается наличие строки для сущности, и она будет обновлена при вызове SaveChanges. Если значение ключа не задано или тип сущности не использует сформированные ключи, то новая сущность все равно будет отслеживаться в состоянии Added, как в предыдущих версиях.

Почему

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

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

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

modelBuilder
    .Entity<Blog>()
    .Property(e => e.Id)
    .ValueGeneratedNever();

Или с помощью заметок к данным:

[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }

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

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

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

До выхода версии 3.0 применяемые EF Core каскадные действия (удаление зависимых сущностей, когда требуемый субъект удален либо отношение с ним было разорвано) не выполнялись до вызова SaveChanges.

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

Начиная с версии 3.0 EF Core применяет каскадные действия сразу после обнаружения условия триггера. Например, вызов context.Remove() для удаления сущности субъекта приведет к немедленной установке всех отслеживаемых связанных необходимых зависимых объектов в состояние Deleted.

Почему

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

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

Для восстановления прежнего поведения можно использовать параметры context.ChangeTracker. Пример:

context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;
context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;

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

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

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

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

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

Почему

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

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

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

**

более четкая семантика DeleteBehavior.Restrict

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

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

До версии 3.0 DeleteBehavior.Restrict создавал внешние ключи в базе данных с помощью семантики Restrict, но также изменял внутреннее исправление неочевидным образом.

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

Начиная с версии 3.0 DeleteBehavior.Restrict обеспечивает создание внешних ключей с помощью семантики Restrict, то есть без каскадов; создается при нарушении ограничения, не влияя на внутреннее исправление EF.

Почему

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

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

Для восстановления прежнего поведения можно использовать DeleteBehavior.ClientNoAction.

Типы запросов объединяются с типами сущностей

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

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

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

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

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

Почему

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

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

Следующие компоненты этого API теперь являются устаревшими:

  • ModelBuilder.Query<>() — вместо него следует вызвать ModelBuilder.Entity<>().HasNoKey(), чтобы пометить тип сущности как не имеющий ключей. Это по-прежнему не следует настраивать посредством соглашения, чтобы избежать ошибки, когда первичный ключ ожидается, но не соответствует соглашению.
  • DbQuery<> — вместо него следует использовать DbSet<>.
  • DbContext.Query<>() — вместо него следует использовать DbContext.Set<>().
  • IQueryTypeConfiguration<TQuery> — вместо него следует использовать IEntityTypeConfiguration<TEntity>.

Примечание

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

Изменение API конфигурации для отношений принадлежащего типа

Отслеживание вопроса 12444Отслеживание вопроса 9148Отслеживание вопроса 14153

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

До выхода EF Core 3.0 конфигурация принадлежащего отношения выполнялась непосредственно после вызова OwnsOne или OwnsMany.

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

Начиная с EF Core 3.0 существует текучий API для настройки свойства навигации для владельца с помощью WithOwner(). Пример:

modelBuilder.Entity<Order>.OwnsOne(e => e.Details).WithOwner(e => e.Order);

Конфигурация, связанная с отношением между владельцем и принадлежащим объектом, теперь должна быть включена в цепочку после WithOwner() по аналогии с настройкой других отношений. При этом конфигурация для принадлежащего типа сама будет включена в цепочку после OwnsOne()/OwnsMany(). Пример:

modelBuilder.Entity<Order>.OwnsOne(e => e.Details, eb =>
    {
        eb.WithOwner()
            .HasForeignKey(e => e.AlternateId)
            .HasConstraintName("FK_OrderDetails");

        eb.ToTable("OrderDetails");
        eb.HasKey(e => e.AlternateId);
        eb.HasIndex(e => e.Id);

        eb.HasOne(e => e.Customer).WithOne();

        eb.HasData(
            new OrderDetails
            {
                AlternateId = 1,
                Id = -1
            });
    });

Кроме того, вызов Entity(), HasOne() или Set() с целевым объектом принадлежащего типа теперь приведет к возникновению исключения.

Почему

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

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

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

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

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

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

Рассмотрим следующую модель:

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public OrderDetails Details { get; set; }
}

public class OrderDetails
{
    public int Id { get; set; }
    public string ShippingAddress { get; set; }
}

До выхода EF Core 3.0, если класс OrderDetails принадлежал классу Order или явно сопоставлялся с этой же таблицей, при добавлении нового класса Order всегда требовался экземпляр OrderDetails.

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

Начиная с версии 3.0 система EF Core позволяет добавлять класс Order без класса OrderDetails и сопоставляет все свойства OrderDetails, за исключением первичного ключа, со столбцами, допускающими значение NULL. При отправке запроса EF Core задает классу OrderDetails значение null, если какому-либо его обязательному свойству не задано значение или если отсутствуют необходимые свойства помимо первичного ключа и все свойства имеют значение null.

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

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

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

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

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

Рассмотрим следующую модель:

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public byte[] Version { get; set; }
    public OrderDetails Details { get; set; }
}

public class OrderDetails
{
    public int Id { get; set; }
    public string ShippingAddress { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Order>()
        .Property(o => o.Version).IsRowVersion().HasColumnName("Version");
}

До выхода EF Core 3.0, если класс OrderDetails принадлежал классу Order или явно сопоставлялся с той же таблицей, то обновление только класса OrderDetails не приводило к обновлению значения Version на клиенте и следующее обновление завершалось ошибкой.

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

Начиная с версии 3.0 система EF Core распространяет новое значение Version для класса Order, если ему принадлежит класс OrderDetails. В противном случае во время проверки модели создается исключение.

Почему

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

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

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

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<OrderDetails>()
        .Property<byte[]>("Version").IsRowVersion().HasColumnName("Version");
}

Собственные сущности нельзя запрашивать без использования запроса отслеживания владельцем

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

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

До версии EF Core 3.0 собственные сущности можно было запрашивать как любую другую навигацию.

context.People.Select(p => p.Address);

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

Начиная с версии 3.0 EF Core выдает исключение, если запрос отслеживания проецирует собственную сущность без владельца.

Почему

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

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

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

В противном случае добавьте вызов AsNoTracking():

context.People.Select(p => p.Address).AsNoTracking();

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

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

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

Рассмотрим следующую модель:

public abstract class EntityBase
{
    public int Id { get; set; }
}

public abstract class OrderBase : EntityBase
{
    public int ShippingAddress { get; set; }
}

public class BulkOrder : OrderBase
{
}

public class Order : OrderBase
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Ignore<OrderBase>();
    modelBuilder.Entity<EntityBase>();
    modelBuilder.Entity<BulkOrder>();
    modelBuilder.Entity<Order>();
}

До выхода EF Core 3.0 свойство ShippingAddress сопоставлялось с отдельными столбцами для классов BulkOrder и Order по умолчанию.

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

Начиная с версии 3.0 система EF Core создает только один столбец для класса ShippingAddress.

Почему

Старое поведение было непредвиденным.

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

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

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Ignore<OrderBase>();
    modelBuilder.Entity<EntityBase>();
    modelBuilder.Entity<BulkOrder>()
        .Property(o => o.ShippingAddress).HasColumnName("BulkShippingAddress");
    modelBuilder.Entity<Order>()
        .Property(o => o.ShippingAddress).HasColumnName("ShippingAddress");
}

Соглашение для свойства внешнего ключа больше не совпадает со свойством субъекта по имени

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

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

Рассмотрим следующую модель:

public class Customer
{
    public int CustomerId { get; set; }
    public ICollection<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
}

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

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

Начиная с версии 3.0 система EF Core не будет пытаться использовать свойства для внешних ключей по соглашению, если они имеют такое же имя, что и свойство субъекта. Шаблоны имени типа субъекта, сцепленного с именем свойства субъекта, и имени навигации, сцепленного с именем свойства субъекта, по-прежнему совпадают. Пример:

public class Customer
{
    public int Id { get; set; }
    public ICollection<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
}
public class Customer
{
    public int Id { get; set; }
    public ICollection<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public int BuyerId { get; set; }
    public Customer Buyer { get; set; }
}

Почему

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

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

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

Соединение с базой данных теперь закрывается, если оно больше не используется до завершения TransactionScope

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

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

До выхода EF Core 3.0, если контекст открывает соединение внутри класса TransactionScope, оно остается открытым пока активен текущий класс TransactionScope.

using (new TransactionScope())
{
    using (AdventureWorks context = new AdventureWorks())
    {
        context.ProductCategories.Add(new ProductCategory());
        context.SaveChanges();

        // Old behavior: Connection is still open at this point

        var categories = context.ProductCategories().ToList();
    }
}

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

Начиная с версии 3.0 система EF Core закрывает соединение сразу же после прекращения его использования.

Почему

Это изменение позволяет использовать несколько контекстов в одном классе TransactionScope. Новое поведение также соответствует EF6.

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

Если соединение должно оставаться открытым, явный вызов метода OpenConnection() гарантирует, что EF Core не закроет его преждевременно.

using (new TransactionScope())
{
    using (AdventureWorks context = new AdventureWorks())
    {
        context.Database.OpenConnection();
        context.ProductCategories.Add(new ProductCategory());
        context.SaveChanges();

        var categories = context.ProductCategories().ToList();
        context.Database.CloseConnection();
    }
}

Каждое свойство использует создание независимых целочисленных ключей в памяти

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

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

До выхода EF Core 3.0 для всех свойств целочисленных ключей в памяти использовался один общий генератор значений.

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

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

Почему

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

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

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

По умолчанию используются резервные поля

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

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

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

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

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

Почему

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

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

Поведение, характерное для версий до 3.0, можно восстановить, настроив режим доступа в ModelBuilder. Пример:

modelBuilder.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);

Исключение при обнаружении нескольких совместимых резервных полей

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

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

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

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

Начиная с EF Core 3.0 при обнаружении нескольких полей для одного свойства возникает исключение.

Почему

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

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

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

modelBuilder
    .Entity<Blog>()
    .Property(e => e.Id)
    .HasField("_id");

Имена свойств, доступных только для полей, должны совпадать с именем поля.

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

До EF Core 3.0 свойство можно было указать с помощью строкового значения. Если свойство с таким именем не удавалось найти в типе .NET, компонент EF Core пытался сопоставить его с полем, используя правила соглашения.

private class Blog
{
    private int _id;
    public string Name { get; set; }
}
modelBuilder
    .Entity<Blog>()
    .Property("Id");

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

Начиная с EF Core 3.0 имя свойства, доступного только для полей, должно точно соответствовать имени поля.

modelBuilder
    .Entity<Blog>()
    .Property("_id");

Почему

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

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

Имена свойств, доступных только для полей, должны совпадать с именем поля, с которым они сопоставляются. В будущем выпуске EF Core после версии 3.0 мы планируем снова включить явную настройку имени поля, которое отличается от имени свойства (см. вопрос № 15307):

modelBuilder
    .Entity<Blog>()
    .Property("Id")
    .HasField("_id");

AddDbContext/AddDbContextPool больше не вызывает метод AddLogging и AddMemoryCache

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

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

До EF Core 3.0 вызов метода AddDbContext или AddDbContextPool также приводил к регистрации служб ведения журналов и кэширования памяти с внедрением зависимостей с помощью вызовов методов AddDbContext и AddDbContextPool.

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

Начиная с версии EF Core 3.0 методы AddDbContext и AddDbContextPool больше не регистрируют такие службы с внедрением зависимостей.

Почему

Для EF Core 3.0 не требуется наличие таких служб в контейнере внедрения зависимостей приложения. Но если интерфейс ILoggerFactory зарегистрирован в контейнере внедрения зависимостей приложения, он будет по-прежнему использоваться EF Core.

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

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

AddEntityFramework* добавляет IMemoryCache с ограниченным размером

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

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

До EF Core 3.0 вызов методов AddEntityFramework* также приводил к регистрации служб кэширования памяти с внедрением зависимостей без ограничения размера.

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

Начиная с версии EF Core 3.0 вызов AddEntityFramework* приводит к регистрации службы IMemoryCache с ограничением размера. Если добавленные позже службы зависят от IMemoryCache, они могут быстро достичь ограничения по умолчанию, что приведет к созданию исключений или снижению производительности.

Почему

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

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

В большинстве случаев вызывать AddEntityFramework* не требуется, если также вызывается метод AddDbContext или AddDbContextPool. Поэтому лучшее решение — удалить вызов AddEntityFramework*.

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

DbContext.Entry теперь выполняет локальную процедуру DetectChanges

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

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

До выхода EF Core 3.0 вызов DbContext.Entry приводил к обнаружению изменений для всех отслеживаемых сущностей. Это гарантировало, что состояние, представленное в EntityEntry, было актуальным.

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

Начиная с EF Core 3.0 при вызове DbContext.Entry будет предприниматься попытка обнаружить изменения только в заданной сущности и всех связанных с ней отслеживаемых сущностях субъектов. Это означает, что изменения в другом месте могут не обнаруживаться при вызове этого метода, что может негативно повлиять на состояние приложения.

Обратите внимание, что если для ChangeTracker.AutoDetectChangesEnabled задано значение false, то будет отключено даже такое локальное обнаружение изменений.

Другие методы, вызывающие обнаружение изменений, например ChangeTracker.Entries и SaveChanges, по-прежнему подразумевают полную процедуру DetectChanges для всех отслеживаемых сущностей.

Почему

Это изменение было внесено для повышения производительности по умолчанию при использовании context.Entry.

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

Вызовите ChangeTracker.DetectChanges() явным образом перед вызовом Entry, чтобы обеспечить поведение, предшествовавшее выходу версии 3.0.

Ключи массива строк и байтов не формируются клиентом по умолчанию

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

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

До выхода EF Core 3.0 свойства ключей string и byte[] можно было использовать без явного указания значения, отличного от NULL. В этом случае значение ключа создается на клиенте в виде GUID, сериализованного до байтов для byte[].

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

Начиная с EF Core 3.0 возникает исключение, указывающее на то, что значение ключа не задано.

Почему

Это изменение было внесено, так как формируемые клиентом значения string/byte[] обычно бесполезны, а поведение по умолчанию затрудняло типовое планирование использования этих значений ключей.

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

Поведение, предшествовавшее выходу версии 3.0, можно получить, явно указав, что свойства ключей должны использовать формируемые значения, если не задано никакое другое значение, отличное от NULL. Например, с помощью текучего API:

modelBuilder
    .Entity<Blog>()
    .Property(e => e.Id)
    .ValueGeneratedOnAdd();

Или с помощью заметок к данным:

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }

ILoggerFactory теперь является службой с ограниченной областью действия

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

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

До выхода EF Core 3.0 ILoggerFactory регистрировалась в качестве отдельной службы.

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

Начиная с EF Core 3.0 ILoggerFactory регистрируется в качестве службы с ограниченной областью действия.

Почему

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

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

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

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

Прокси с отложенной загрузкой больше не предполагают полную загрузку свойств навигации

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

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

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

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

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

Почему

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

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

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

Чрезмерное создание внутренних поставщиков служб теперь является ошибкой по умолчанию

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

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

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

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

Начиная с EF Core 3.0 это предупреждение считается ошибкой и возникает исключение.

Почему

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

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

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

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .ConfigureWarnings(w => w.Log(CoreEventId.ManyServiceProvidersCreatedWarning));
}

Новое поведение для вызова HasOne/HasMany с одной строкой

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

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

До версии EF Core 3.0 интерпретация кода с вызовом HasOne или HasMany с одной строкой была очень нечеткой. Пример:

modelBuilder.Entity<Samurai>().HasOne("Entrance").WithOne();

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

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

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

Начиная с EF Core 3.0, указанный выше код выполняет те действия, которые должен был выполнять раньше.

Почему

Старое поведение создавало большую путаницу, особенно при считывании кода конфигурации и при поиске ошибок.

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

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

modelBuilder.Entity<Samurai>().HasOne("Some.Entity.Type.Name", null).WithOne();

Тип возвращаемого значения для нескольких асинхронных методов изменен с Task на ValueTask

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

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

Следующие асинхронные методы ранее возвращали Task<T>:

  • DbContext.FindAsync()
  • DbSet.FindAsync()
  • DbContext.AddAsync()
  • DbSet.AddAsync()
  • ValueGenerator.NextValueAsync() (и производные классы)

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

Упомянутые выше методы теперь возвращают ValueTask<T> над тем же T, что и раньше.

Почему

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

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

Приложения, просто ожидающие приведенные выше API, нужно просто перекомпилировать — изменять источник не требуется. Для более сложного варианта использования (например, передачи возвращаемого класса Task методу Task.WhenAny()) обычно требуется, чтобы возвращаемый класс ValueTask<T> был преобразован в класс Task<T> путем вызова в нем метода AsTask(). Обратите внимание, что это сводит к нулю сокращение числа выделений, которое возможно в рамках этого изменения.

Заметка Relational:TypeMapping теперь является просто TypeMapping

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

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

Для заметок сопоставлений типов использовалось имя "Relational:TypeMapping".

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

Теперь для заметок сопоставлений типов используется имя "TypeMapping".

Почему

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

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

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

ToTable для производного типа выдает исключение

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

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

До выхода EF Core 3.0 вызов ToTable() для производного типа игнорировался, так как единственной стратегией сопоставления наследования был метод "одна таблица на иерархию", где такая операция недействительна.

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

Начиная с EF Core 3.0 и в рамках подготовки для добавления поддержки методов "одна таблица на тип" и "одна таблица на каждый отдельный тип/класс" в последующем выпуске вызов ToTable() для производного типа теперь будет выдавать исключение, чтобы избежать непредвиденного изменения сопоставления в будущем.

Почему

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

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

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

ForSqlServerHasIndex заменен на HasIndex

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

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

До выхода EF Core 3.0 ForSqlServerHasIndex().ForSqlServerInclude() предоставлял способ настройки столбцов, используемых с INCLUDE.

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

Начиная с EF Core 3.0 использование Include для индекса теперь поддерживается на реляционном уровне. Используйте ключевое слово HasIndex().ForSqlServerInclude().

Почему

Это изменение было внесено, чтобы консолидировать API для индексов с Include в одном месте для всех поставщиков баз данных.

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

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

Изменения API метаданных

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

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

Следующие свойства были преобразованы в методы расширения:

  • IEntityType.QueryFilter ->GetQueryFilter()
  • IEntityType.DefiningQuery ->GetDefiningQuery()
  • IProperty.IsShadowProperty ->IsShadowProperty()
  • IProperty.BeforeSaveBehavior ->GetBeforeSaveBehavior()
  • IProperty.AfterSaveBehavior ->GetAfterSaveBehavior()

Почему

Это изменение упрощает реализацию упомянутых выше интерфейсов.

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

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

Изменения API метаданных с учетом поставщика

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

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

Методы расширения с учетом поставщика будут приведены в соответствие:

  • IProperty.Relational().ColumnName ->IProperty.GetColumnName()
  • IEntityType.SqlServer().IsMemoryOptimized ->IEntityType.IsMemoryOptimized()
  • PropertyBuilder.UseSqlServerIdentityColumn() ->PropertyBuilder.UseIdentityColumn()

Почему

Это изменение упрощает реализацию упомянутых выше методов расширения.

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

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

EF Core больше не отправляет pragma для принудительного применения FK SQLite

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

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

До выхода версии 3.0 система EF Core отправляла PRAGMA foreign_keys = 1 при открытии соединения с SQLite.

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

Начиная с версии 3.0 система EF Core больше не отправляет PRAGMA foreign_keys = 1 при открытии соединения с SQLite.

Почему

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

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

Внешние ключи включены по умолчанию в SQLitePCLRaw.bundle_e_sqlite3, который по умолчанию используется для EF Core. В других случаях внешние ключи можно включить, указав Foreign Keys=True в строке подключения.

Теперь Microsoft.EntityFrameworkCore.Sqlite зависит от SQLitePCLRaw.bundle_e_sqlite3

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

До выхода версии 3.0 система EF Core использовала SQLitePCLRaw.bundle_green.

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

Начиная с версии 3.0 система EF Core использует SQLitePCLRaw.bundle_e_sqlite3.

Почему

Это изменение было внесено, чтобы версия SQLite, используемая в iOS, была согласована с другими платформами.

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

Чтобы использовать собственную версию SQLite в iOS, настройте Microsoft.Data.Sqlite для использования другого пакета SQLitePCLRaw.

Теперь значения Guid хранятся в SQLite в виде значений типа TEXT

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

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

В прошлом значения Guid хранились в SQLite в виде больших двоичных объектов.

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

Значения GUID теперь хранятся в виде значений типа TEXT.

Почему

Двоичный формат типа Guid не стандартизирован. Хранение значений в виде значений типа TEXT повышает уровень совместимости базы данных с другими технологиями.

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

Существующие базы данных можно перенести в новый формат, выполнив код SQL следующим образом.

UPDATE MyTable
SET GuidColumn = hex(substr(GuidColumn, 4, 1)) ||
                 hex(substr(GuidColumn, 3, 1)) ||
                 hex(substr(GuidColumn, 2, 1)) ||
                 hex(substr(GuidColumn, 1, 1)) || '-' ||
                 hex(substr(GuidColumn, 6, 1)) ||
                 hex(substr(GuidColumn, 5, 1)) || '-' ||
                 hex(substr(GuidColumn, 8, 1)) ||
                 hex(substr(GuidColumn, 7, 1)) || '-' ||
                 hex(substr(GuidColumn, 9, 2)) || '-' ||
                 hex(substr(GuidColumn, 11, 6))
WHERE typeof(GuidColumn) == 'blob';

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

modelBuilder
    .Entity<MyEntity>()
    .Property(e => e.GuidProperty)
    .HasConversion(
        g => g.ToByteArray(),
        b => new Guid(b));

Microsoft.Data.Sqlite по-прежнему может читать значения Guid из столбцов BLOB и TEXT. Но поскольку изменился формат по умолчанию для параметров и констант, скорее всего, вам потребуется выполнить некоторые действия в большинстве сценариев, где используются значения Guid.

Теперь значения типа Char хранятся в SQLite в виде значений типа TEXT

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

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

Ранее значения типа Char хранились в SQLite в виде значений типа INTEGER. Например, значение типа Char A хранилось как целочисленное значение 65.

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

Теперь значения типа char хранятся в виде значений типа TEXT.

Почему

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

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

Существующие базы данных можно перенести в новый формат, выполнив код SQL следующим образом.

UPDATE MyTable
SET CharColumn = char(CharColumn)
WHERE typeof(CharColumn) = 'integer';

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

modelBuilder
    .Entity<MyEntity>()
    .Property(e => e.CharProperty)
    .HasConversion(
        c => (long)c,
        i => (char)i);

Microsoft.Data.Sqlite также сохраняет возможность чтения значений символов из столбцов INTEGER и TEXT, поэтому для реализации определенных сценариев может не потребоваться никаких действий.

Идентификаторы миграции теперь создаются с использованием календаря инвариантного языка и региональных параметров

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

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

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

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

Теперь идентификаторы миграций всегда создаются с использованием календаря инвариантного языка и региональных параметров (григорианского).

Почему

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

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

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

Идентификатор миграции можно найти в атрибуте Migration в файлы конструктора миграций.

 [DbContext(typeof(MyDbContext))]
-[Migration("25620318122820_MyMigration")]
+[Migration("20190318122820_MyMigration")]
 partial class MyMigration
 {

Кроме того, потребуется обновить таблицу журнала миграций.

UPDATE __EFMigrationsHistory
SET MigrationId = CONCAT(LEFT(MigrationId, 4)  - 543, SUBSTRING(MigrationId, 4, 150))

Удален метод UseRowNumberForPaging

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

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

До выхода EF Core 3.0 UseRowNumberForPaging можно было использовать для создания кода SQL для разбиения на страницы, совместимого с SQL Server 2008.

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

Начиная с EF Core 3.0, EF будет формировать код SQL для разбиения на страницы, который совместим только с более поздними версиями SQL Server.

Почему

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

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

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

Сведения о расширении и его метаданные были удалены из интерфейса IDbContextOptionsExtension

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

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

Интерфейс IDbContextOptionsExtension содержал методы для предоставления метаданных расширения.

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

Эти методы были перемещены в новый абстрактный базовый класс DbContextOptionsExtensionInfo, который возвращается новым свойством IDbContextOptionsExtension.Info.

Почему

В период между выпусками 2.0 и 3.0 нам пришлось несколько раз добавлять и изменять эти методы. Их выделение в новый абстрактный базовый класс упростит реализацию таких изменений и позволит вносить их без нарушения работы существующих расширений.

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

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

Оператор LogQueryPossibleExceptionWithAggregateOperator был переименован

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

Change

RelationalEventId.LogQueryPossibleExceptionWithAggregateOperator переименован в RelationalEventId.LogQueryPossibleExceptionWithAggregateOperatorWarning.

Почему

Выравнивает именование этого события предупреждения с другими событиями предупреждения.

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

Используйте новое имя. (Обратите внимание, что номер события не изменен.)

Уточнение API для имен ограничений внешнего ключа

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

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

До EF Core 3.0 имена ограничений внешнего ключа назывались просто именами. Пример:

var constraintName = myForeignKey.Name;

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

Начиная с EF Core 3.0 имена ограничений внешнего ключа называются "имя ограничения". Пример:

var constraintName = myForeignKey.ConstraintName;

Почему

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

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

Используйте новое имя.

Методы IRelationalDatabaseCreator.HasTables/HasTablesAsync стали общедоступными

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

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

До выпуска версии EF Core 3.0 эти методы были защищены.

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

Начиная с EF Core 3.0 эти методы стали общедоступными.

Почему

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

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

Измените уровень доступа для всех переопределений.

Microsoft.EntityFrameworkCore.Design теперь является пакетом DevelopmentDependency

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

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

До выпуска EF Core 3.0 мы предоставляли Microsoft.EntityFrameworkCore.Design в виде обычного пакета NuGet, на сборку которого могли ссылаться зависящие от него проекты.

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

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

Почему

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

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

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

<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.0.0">
  <PrivateAssets>all</PrivateAssets>
  <!-- Remove IncludeAssets to allow compiling against the assembly -->
  <!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
</PackageReference>

Если ссылка на пакет указывается транзитивно через Microsoft.EntityFrameworkCore.Tools, вам нужно добавить явную ссылку PackageReference на пакет для изменения его метаданных. Такую явную ссылку необходимо добавить в любой проект, где требуются типы из пакета.

Библиотека SQLitePCL.raw обновлена до версии 2.0.0

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

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

Пакет Microsoft.EntityFrameworkCore.Sqlite ранее зависел от библиотеки SQLitePCL.raw версии 1.1.12.

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

Мы обновили пакет, и теперь он зависит от версии 2.0.0.

Почему

Версия 2.0.0 библиотеки SQLitePCL.raw работает с .NET Standard 2.0. Ранее она работала с .NET Standard 1.1, для чего требовалось выполнять крупное закрытие транзитивных пакетов.

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

SQLitePCL.raw версии 2.0.0 включает некоторые критические изменения. Подробные сведения см. в заметках о выпуске.

Обновление NetTopologySuite до версии 2.0.0

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

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

Пространственные пакеты раньше зависели от версии NetTopologySuite 1.15.1.

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

Мы обновили пакет, и теперь он зависит от версии 2.0.0.

Почему

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

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

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

Вместо System.Data.SqlClient используется Microsoft.Data.SqlClient

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

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

Пакет Microsoft.EntityFrameworkCore.SqlServer ранее зависел от System.Data.SqlClient.

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

Мы обновили пакет, и теперь он зависит от Microsoft.Data.SqlClient.

Почему

Microsoft.Data.SqlClient теперь является основным разрабатываемым драйвером для доступа к SQL Server вместо System.Data.SqlClient. Некоторые важные функции, такие как Always Encrypted, доступны только в Microsoft.Data.SqlClient.

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

Если код включает прямую зависимость от System.Data.SqlClient, необходимо изменить код так, чтобы он ссылался на Microsoft.Data.SqlClient. Поскольку API двух пакетов очень хорошо совместимы, достаточно изменить пакет и пространство имен.

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

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

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

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

public class User
{
        public Guid Id { get; set; }
        public User CreatedBy { get; set; }
        public User UpdatedBy { get; set; }
        public Guid CreatedById { get; set; }
        public Guid? UpdatedById { get; set; }
}

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

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

Почему

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

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

Выполняйте полную настройку связи. Пример:

modelBuilder
     .Entity<User>()
     .HasOne(e => e.CreatedBy)
     .WithMany();

 modelBuilder
     .Entity<User>()
     .HasOne(e => e.UpdatedBy)
     .WithMany();

DbFunction.Schema в виде нулевой или пустой строки выполняет настройку для включения в схему по умолчанию для модели

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

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

DbFunction, настроенная со схемой в виде пустой строки, обрабатывалась как встроенная функция без схемы. Например, следующий код сопоставит функцию CLR DatePart со встроенной функцией DATEPART в SqlServer.

[DbFunction("DATEPART", Schema = "")]
public static int? DatePart(string datePartArg, DateTime? date) => throw new Exception();

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

Все сопоставления DbFunction считаются сопоставленными с определяемыми пользователем функциями. Поэтому пустое строковое значение поместит функцию в схему по умолчанию для модели. Это может быть схема, настроенная явным образом с помощью modelBuilder.HasDefaultSchema() текучего API либо dbo.

Почему

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

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

Вручную настройте преобразование функции DbFunction, чтобы сопоставить ее со встроенной функцией.

modelBuilder
    .HasDbFunction(typeof(MyContext).GetMethod(nameof(MyContext.DatePart)))
    .HasTranslation(args => SqlFunctionExpression.Create("DatePart", args, typeof(int?), null));

Версия EF Core 3.0 больше предназначена для .NET Standard 2.1, а не для .NET Standard 2.0 отменено

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

Версия EF Core 3.0 была больше предназначена для .NET Standard 2.1. Это критическое изменение, которое исключало поддержку приложений .NET Framework. Это изменение отменено в EF Core 3.1, и EF Core снова ориентируется на .NET Standard 2.0.

Выполнение запроса с ведением журнала на уровне отладки Debug отменено

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

Причиной послужило то, что новая конфигурация EF Core 3.0 позволяет приложению указывать уровень ведения журнала для любого события. Например, чтобы переключить ведение журналов с уровня SQL на Debug, явно настройте уровень в OnConfiguring или AddDbContext:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlServer(connectionString)
        .ConfigureWarnings(c => c.Log((RelationalEventId.CommandExecuting, LogLevel.Debug)));