Критические изменения в EF Core 3.x
Указанные ниже изменения API и поведения могут нарушать работу существующих приложений при их обновлении до версии 3.x. Изменения, которые, по нашим ожиданиям, повлияют только на поставщиков баз данных, описаны в разделе изменений для поставщиков.
Итоги
Изменения высокой степени влияния
Запросы 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
Старое поведение
До версии 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 были переименованы
Внимание
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 не поддерживает составление при использовании с хранимой процедурой
Старое поведение
До версии 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 можно указать только в корневых элементах запроса
Старое поведение
До EF Core версии 3.0 метод FromSql
можно было определять в любом месте в запросе.
Новое поведение
Начиная с EF Core версии 3.0, новые методы FromSqlRaw
и FromSqlInterpolated
(которые заменяют FromSql
) можно указывать только в корневых элементах запроса, т. е. непосредственно в DbSet<>
. Попытка указать их в любом другом месте приведет к ошибке компиляции.
Почему
Определение FromSql
в любом месте за пределами DbSet
не предоставляет никаких преимуществ и может привести к неоднозначности в некоторых сценариях.
Устранение проблем
Вызовы FromSql
нужно поместить непосредственно в элемент DbSet
, к которому они применяются.
Запросы без отслеживания больше не выполняют разрешение идентификаторов
Старое поведение
До EF Core 3.0 один и тот же экземпляр сущности будет использоваться для каждого вхождения сущности с заданным типом и ИД. Это соответствует поведению запросов отслеживания. Например, следующий запрос:
var results = context.Products.Include(e => e.Category).AsNoTracking().ToList();
возвращает тот же экземпляр Category
для каждого Product
, связанного с заданной категорией.
Новое поведение
Начиная с EF Core 3.0, при обнаружении сущности с заданным типом и ИД в разных местах возвращенного графа будут создаваться различные экземпляры сущности. Например, вышеприведенный запрос теперь будет возвращать новый экземпляр Category
для каждого Product
, даже если два продукта связаны с одной категорией.
Почему
Разрешение идентификаторов (то есть определение того, что сущность имеет тот же тип и идентификатор, что и обнаруженная ранее сущность) добавляет дополнительную нагрузку на производительность и память. Обычно это идет вразрез с причинами использования запросов без отслеживания. Кроме того, хотя разрешение идентификаторов иногда может быть полезным, оно не требуется, если сущности должны быть сериализованы и отправлены клиенту, что является распространенным случаем для неотслеживаемых запросов.
Устранение проблем
Используйте запрос с отслеживанием, если требуется разрешение идентификаторов.
Временные значения ключа больше не устанавливаются для экземпляров сущностей
Старое поведение
До выхода EF Core 3.0 временные значения назначались всем свойствам ключей, которые позднее получили бы реальное значение, созданное базой данных. Обычно эти временные значения были большими отрицательными числами.
Новое поведение
Начиная с версии 3.0 EF Core сохраняет временное значение ключа как часть сведений об отслеживании сущности, оставляя само свойство ключа без изменений.
Почему
Это изменение было внесено для ситуации, когда временные значения ключей ошибочно становились постоянными из-за перемещения сущности, которая ранее отслеживалась некоторым экземпляром DbContext
, в другой экземпляр DbContext
.
Устранение проблем
Приложения, которые назначают значения первичного ключа внешним ключам для формирования ассоциаций между сущностями, могут использовать старое поведение, если первичные ключи сформированы хранилищем и принадлежат сущностям в состоянии Added
.
Этого можно избежать следующим образом:
- Отказ от использования ключей, сформированных хранилищем.
- Настройка свойств навигации для формирования отношений вместо задания значений внешнего ключа.
- Получение фактических временных значений ключа из сведений об отслеживании сущности.
Например,
context.Entry(blog).Property(e => e.Id).CurrentValue
возвратит временное значение, даже если самblog.Id
не был задан.
DetectChanges учитывает значения ключей, сформированные хранилищем
Старое поведение
До выпуска 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; }
Каскадные удаления теперь по умолчанию выполняются немедленно
Старое поведение
До выхода версии 3.0 применяемые EF Core каскадные действия (удаление зависимых сущностей, когда требуемый субъект удален либо отношение с ним было разорвано) не выполнялись до вызова SaveChanges.
Новое поведение
Начиная с версии 3.0 EF Core применяет каскадные действия сразу после обнаружения условия триггера.
Например, вызов context.Remove()
для удаления сущности субъекта приведет к немедленной установке всех отслеживаемых связанных необходимых зависимых объектов в состояние Deleted
.
Почему
Это изменение было внесено, чтобы улучшить работу в сценариях аудита и привязки данных, где важно понимать, какие сущности будут удалены перед вызовом SaveChanges
.
Устранение проблем
Для восстановления прежнего поведения можно использовать параметры context.ChangeTracker
.
Например:
context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;
context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;
Безотложная загрузка связанных сущностей теперь происходит в одном запросе
Старое поведение
До версии 3.0 безотложная загрузка навигаций по коллекциям с помощью операторов Include
приводила к созданию нескольких запросов к реляционной базе данных, по одной для каждого связанного типа сущности.
Новое поведение
Начиная с версии 3.0 платформа EF Core создает один запрос с соединениями к реляционным базам данных.
Почему
Отправка нескольких запросов для реализации одного запроса LINQ приводила ко множеству проблем, включая снижение производительности из-за необходимости нескольких циклов обращения к базе данных и проблем с согласованностью данных, так как при каждом запросе состояние базы данных могло быть разным.
Устранение проблем
Хотя технически это изменение не является критическим, оно может существенно повлиять на производительность приложения в случае, если один запрос содержит большое число операторов Include
с навигациями по коллекциям. Дополнительные сведения и указания по повышению эффективности запросов см. в этом комментарии.
**
Более четкая семантика DeleteBehavior.Restrict
Старое поведение
До версии 3.0 DeleteBehavior.Restrict
создавал внешние ключи в базе данных с помощью семантики Restrict
, но также изменял внутреннее исправление неочевидным образом.
Новое поведение
Начиная с версии 3.0 DeleteBehavior.Restrict
обеспечивает создание внешних ключей с помощью семантики Restrict
, то есть без каскадов; создается при нарушении ограничения, не влияя на внутреннее исправление EF.
Почему
Это изменение было внесено, чтобы упростить работу с помощью более интуитивного использования DeleteBehavior
без непредвиденных побочных эффектов.
Устранение проблем
Для восстановления прежнего поведения можно использовать DeleteBehavior.ClientNoAction
.
Типы запросов объединяются с типами сущностей
Старое поведение
До выпуска EF Core 3.0 типы запросов представляли собой средства для запроса данных, не определяющие первичный ключ структурированным образом. То есть тип запроса использовался для сопоставления типов сущностей без ключей (скорее всего, из представления, но, возможно, и из таблицы), а обычный тип сущности использовался при наличии ключа (скорее всего, из таблицы, но, возможно, и из представления).
Новое поведение
Теперь тип запроса стал просто типом сущности без первичного ключа. Типы сущностей без ключей имеют ту же функциональность, что типы запросов в предыдущих версиях.
Почему
Это изменение было внесено, чтобы избежать путаницы с назначением типов запросов. В частности, они представляют собой типы сущностей без ключей и поэтому по определению доступны только для чтения, однако их не следует использовать только потому, что тип сущности должен быть доступен только для чтения. Аналогично, их часто сопоставляют с представлениями, но только потому, что представления часто не определяют ключи.
Устранение проблем
Следующие компоненты этого API теперь являются устаревшими:
ModelBuilder.Query<>()
— вместо него следует вызватьModelBuilder.Entity<>().HasNoKey()
, чтобы пометить тип сущности как не имеющий ключей. Это по-прежнему не следует настраивать посредством соглашения, чтобы избежать ошибки, когда первичный ключ ожидается, но не соответствует соглашению.DbQuery<>
— вместо него следует использоватьDbSet<>
.DbContext.Query<>()
— вместо него следует использоватьDbContext.Set<>()
.IQueryTypeConfiguration<TQuery>
— вместо него следует использоватьIEntityTypeConfiguration<TEntity>
.
Примечание.
Из-за проблемы в версиях 3.x при запросе сущностей без ключей, все свойства которых имеют значение null
, возвращается 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, как показано в приведенном выше примере.
Зависимые сущности, имеющие общую с субъектом таблицу, теперь являются необязательными
Старое поведение
Рассмотрим следующую модель :
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
.
Все сущности, имеющие общую таблицу со столбцом маркера параллелизма, должны сопоставлять ее со свойством
Старое поведение
Рассмотрим следующую модель :
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");
}
Собственные сущности нельзя запрашивать без использования запроса отслеживания владельцем
Старое поведение
До версии EF Core 3.0 собственные сущности можно было запрашивать как любую другую навигацию.
context.People.Select(p => p.Address);
Новое поведение
Начиная с версии 3.0 EF Core выдает исключение, если запрос отслеживания проецирует собственную сущность без владельца.
Почему
Выполнять операции с собственными сущностями без владельца невозможно, поэтому в подавляющем большинстве случаев подобные запросы к ним являются ошибкой.
Устранение проблем
Если собственную сущность следует отслеживать для изменения в последующем, владелец должен быть включен в запрос.
В противном случае добавьте вызов AsNoTracking()
:
context.People.Select(p => p.Address).AsNoTracking();
Наследуемые свойства из несопоставленных типов теперь сопоставляются с одним столбцом для всех производных типов
Старое поведение
Рассмотрим следующую модель :
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");
}
Соглашение для свойства внешнего ключа больше не сопоставляет то же имя, что у свойства субъекта
Старое поведение
Рассмотрим следующую модель :
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
Старое поведение
До выхода 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();
}
}
Каждое свойство использует создание независимых целочисленных ключей в памяти
Старое поведение
До выхода EF Core 3.0 для всех свойств целочисленных ключей в памяти использовался один общий генератор значений.
Новое поведение
Начиная с EF Core 3.0 при использовании выполняющейся в памяти базы данных каждое свойство целочисленного ключа получает свой собственный генератор значений. Кроме того, при удалении базы данных формирование ключа сбрасывается для всех таблиц.
Почему
Это изменение было внесено, чтобы лучше согласовать формирование ключей в памяти с реальным процессом в базе данных, а также улучшить возможность изоляции тестов друг от друга при использовании выполняющейся в памяти базы данных.
Устранение проблем
Это изменение может нарушить работу приложения, полагающегося на задание определенных значений ключа в памяти. Рекомендуется не полагаться на конкретные значения ключей либо реализовать новое поведение.
По умолчанию используются резервные поля
Старое поведение
До выхода версии 3.0, даже если резервное поле для свойства было известно, система EF Core все равно по умолчанию считывала и записывала значение свойства с помощью методов получения и задания свойства. Исключением из этого было выполнение запроса, где известное резервное поле устанавливается напрямую.
Новое поведение
Начиная с EF Core 3.0, если резервное поле для свойства известно, то EF Core всегда будет использовать его для чтения и записи этого свойства. Это может нарушить работу приложения, если оно полагается на дополнительное поведение, закодированное в методы получения и задания.
Почему
Это изменение было внесено, чтобы не позволить EF Core ошибочно активировать бизнес-логику по умолчанию при выполнении операций базы данных, затрагивающих сущности.
Устранение проблем
Поведение, характерное для версий до 3.0, можно восстановить, настроив режим доступа в ModelBuilder
.
Например:
modelBuilder.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);
Исключение при обнаружении нескольких совместимых резервных полей
Старое поведение
До выхода 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
Старое поведение
До EF Core 3.0 вызов метода AddDbContext
или AddDbContextPool
также приводил к регистрации служб ведения журналов и кэширования памяти с внедрением зависимостей с помощью вызовов методов AddLogging и AddMemoryCache.
Новое поведение
Начиная с версии EF Core 3.0 методы AddDbContext
и AddDbContextPool
больше не регистрируют такие службы с внедрением зависимостей.
Почему
Для EF Core 3.0 не требуется наличие таких служб в контейнере внедрения зависимостей приложения. Но если интерфейс ILoggerFactory
зарегистрирован в контейнере внедрения зависимостей приложения, он будет по-прежнему использоваться EF Core.
Устранение проблем
Если для работы вашего приложения нужны такие службы, явно зарегистрируйте их в контейнере внедрения зависимостей с помощью метода AddLogging или AddMemoryCache.
AddEntityFramework* добавляет IMemoryCache с ограниченным размером
Старое поведение
До EF Core 3.0 вызов методов AddEntityFramework*
также приводил к регистрации служб кэширования памяти с внедрением зависимостей без ограничения размера.
Новое поведение
Начиная с версии EF Core 3.0 вызов AddEntityFramework*
приводит к регистрации службы IMemoryCache с ограничением размера. Если добавленные позже службы зависят от IMemoryCache, они могут быстро достичь ограничения по умолчанию, что приведет к созданию исключений или снижению производительности.
Почему
Использование IMemoryCache без ограничения может привести к неконтролируемому использованию памяти, если в логике кэширования запросов есть ошибка или запросы создаются динамически. Ограничение по умолчанию позволяет предотвращать возможные атаки типа "отказ в обслуживании".
Устранение проблем
В большинстве случаев вызывать AddEntityFramework*
не требуется, если также вызывается метод AddDbContext
или AddDbContextPool
. Поэтому лучшее решение — удалить вызов AddEntityFramework*
.
Если для работы вашего приложения нужны эти службы, заранее зарегистрируйте реализацию IMemoryCache явным образом в контейнере внедрения зависимостей с помощью метода AddMemoryCache.
DbContext.Entry теперь выполняет локальную процедуру DetectChanges
Старое поведение
До выхода 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.
Ключи массива строк и байтов не формируются клиентом по умолчанию
Старое поведение
До выхода 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 теперь является службой с ограниченной областью действия
Старое поведение
До выхода EF Core 3.0 ILoggerFactory
регистрировалась в качестве отдельной службы.
Новое поведение
Начиная с EF Core 3.0 ILoggerFactory
регистрируется в качестве службы с ограниченной областью действия.
Почему
Это изменение было внесено, чтобы разрешить сопоставление средства ведения журнала с экземпляром DbContext
, что обеспечивает другие функции и устраняет некоторые патологические варианты поведения, такие как взрывной рост внутренних поставщиков служб.
Устранение проблем
Это изменение не должно затрагивать код приложения, если только он не регистрирует и использует настраиваемые службы во внутреннем поставщике служб EF Core.
Это нетипично.
В таких случаях большинство возможностей продолжают работать, но потребуется изменить все отдельные службы, которые зависели от ILoggerFactory
, чтобы получить ILoggerFactory
другим способом.
При возникновении подобных ситуаций зарегистрируйте проблему в средстве отслеживания вопросов EF Core на сайте GitHub и сообщите нам, как вы используете ILoggerFactory
, чтобы мы могли лучше понять, как избежать подобных ошибок в будущем.
Прокси с отложенной загрузкой больше не предполагают полную загрузку свойств навигации
Старое поведение
До выхода EF Core 3.0 после удаления DbContext
не существовало способа узнать, было ли указанное свойство навигации для сущности, полученной из этого контекста, загружено полностью.
Вместо это прокси предполагали, что навигация по ссылке загружена, если она имеет отличное NULL значение, а навигация по коллекции загружена, если она не является пустой.
В этом случае попытка отложенной загрузки приводила к холостой операции.
Новое поведение
Начиная с EF Core 3.0 прокси отслеживают, загружено ли свойство навигации. Это означает, что попытка обращения к свойству навигации, загружаемому после удаления контекста, всегда будет давать холостую операцию, даже если загруженное свойство навигации пусто или равно NULL. И наоборот, при попытке обращения к незагруженному свойству навигации, когда контекст удален, возникает исключение, даже если это свойство навигации является непустой коллекцией. Подобная ситуация указывает на то, что код приложения пытается использовать отложенную загрузку в недопустимое время, и нужно запретить это, изменив приложение.
Почему
Это изменение было внесено, чтобы обеспечить правильное и согласованное поведение при попытке отложенной загрузки для удаленного экземпляра DbContext
.
Устранение проблем
Обновите код приложения, чтобы не выполнять попытку отложенной загрузки с удаленным контекстом, либо настройте соответствующую холостую операцию, как описано в сообщении об исключении.
Создание слишком многих внутренних поставщиков служб теперь по умолчанию является ошибкой
Старое поведение
До выхода EF Core 3.0 для приложения, создающего чрезмерное количество внутренних поставщиков служб, регистрировалось предупреждение.
Новое поведение
Начиная с EF Core 3.0 это предупреждение считается ошибкой и возникает исключение.
Почему
Это изменение было внесено для улучшения кода приложения посредством явного указания на подобный патологический случай.
Устранение проблем
При возникновении этой ошибки правильнее всего выявить первопричину и остановить создание такого большого числа внутренних поставщиков служб.
Тем не менее эту ошибку можно преобразовать обратно в предупреждение (или игнорировать) с помощью конфигурации для DbContextOptionsBuilder
.
Например:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.ConfigureWarnings(w => w.Log(CoreEventId.ManyServiceProvidersCreatedWarning));
}
Новое поведение для вызова HasOne/HasMany с одной строкой
Старое поведение
До версии 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
Старое поведение
Следующие асинхронные методы ранее возвращали 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
Старое поведение
Для заметок сопоставлений типов использовалось имя "Relational:TypeMapping".
Новое поведение
Теперь для заметок сопоставлений типов используется имя "TypeMapping".
Почему
Сопоставления типов теперь используются не только для поставщиков реляционных баз данных.
Устранение проблем
Это изменение нарушит работу только тех приложений, которые обращаются к сопоставлению типов непосредственно по заметке, что встречается довольно редко. Наиболее подходящий способ для устранения этой проблемы заключается в использовании поверхности API для доступа к сопоставлениям типов вместо прямого использования заметки.
ToTable для производного типа выдает исключение
Старое поведение
До выхода EF Core 3.0 вызов ToTable()
для производного типа игнорировался, так как единственной стратегией сопоставления наследования был метод "одна таблица на иерархию", где такая операция недействительна.
Новое поведение
Начиная с EF Core 3.0 и в рамках подготовки для добавления поддержки методов "одна таблица на тип" и "одна таблица на каждый отдельный тип/класс" в последующем выпуске вызов ToTable()
для производного типа теперь будет выдавать исключение, чтобы избежать непредвиденного изменения сопоставления в будущем.
Почему
Сейчас сопоставлять производный тип с другой таблицей запрещено. Это изменение позволяет избежать нарушения работы в будущем, когда подобная операция будет разрешена.
Устранение проблем
Удалите все попытки сопоставить производные типы с другими таблицами.
ForSqlServerHasIndex заменен на HasIndex
Старое поведение
До выхода EF Core 3.0 ForSqlServerHasIndex().ForSqlServerInclude()
предоставлял способ настройки столбцов, используемых с INCLUDE
.
Новое поведение
Начиная с EF Core 3.0 использование Include
для индекса теперь поддерживается на реляционном уровне.
Используйте HasIndex().ForSqlServerInclude()
.
Почему
Это изменение было внесено, чтобы консолидировать API для индексов с Include
в одном месте для всех поставщиков баз данных.
Устранение проблем
Используйте новый API, как показано выше.
Изменения API метаданных
Новое поведение
Следующие свойства были преобразованы в методы расширения:
IEntityType.QueryFilter
->GetQueryFilter()
IEntityType.DefiningQuery
->GetDefiningQuery()
IProperty.IsShadowProperty
->IsShadowProperty()
IProperty.BeforeSaveBehavior
->GetBeforeSaveBehavior()
IProperty.AfterSaveBehavior
->GetAfterSaveBehavior()
Почему
Это изменение упрощает реализацию упомянутых выше интерфейсов.
Устранение проблем
Используйте новые методы расширения.
Изменения API метаданных с учетом поставщика
Новое поведение
Методы расширения с учетом поставщика будут приведены в соответствие:
IProperty.Relational().ColumnName
->IProperty.GetColumnName()
IEntityType.SqlServer().IsMemoryOptimized
->IEntityType.IsMemoryOptimized()
PropertyBuilder.UseSqlServerIdentityColumn()
->PropertyBuilder.UseIdentityColumn()
Почему
Это изменение упрощает реализацию упомянутых выше методов расширения.
Устранение проблем
Используйте новые методы расширения.
EF Core больше не отправляет PRAGMA для принудительного применения внешних ключей SQLite
Старое поведение
До выхода версии 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
Старое поведение
В прошлом значения 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
Старое поведение
Значения char ранее хранились в виде значений INTEGER в SQLite. Например, значение типа 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, поэтому для реализации определенных сценариев может не потребоваться никаких действий.
Идентификаторы миграции теперь создаются с использованием календаря инвариантных языка и региональных параметров
Старое поведение
Идентификаторы миграции непреднамеренно создавались с использованием календаря текущего языка и региональных параметров.
Новое поведение
Теперь идентификаторы миграций всегда создаются с использованием календаря инвариантного языка и региональных параметров (григорианского).
Почему
Порядок миграций играет важную роль при обновлении базы данных или разрешении конфликтов слияния. Использование инвариантного календаря позволяет избежать проблем с порядком выполнения, которые могут быть связаны с наличием разных системных календарей у членов группы.
Устранение проблем
Это изменение затрагивает всех, кто использует календари, отличные от григорианского, в которых значение года больше, чем в григорианском (например, тайский (буддистский) календарь). Имеющиеся идентификаторы миграций следует обновить, чтобы новые миграции выполнялись после существующих.
Идентификатор миграции можно найти в атрибуте 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
Старое поведение
До выхода 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
Старое поведение
Интерфейс IDbContextOptionsExtension
содержал методы для предоставления метаданных расширения.
Новое поведение
Эти методы были перемещены в новый абстрактный базовый класс DbContextOptionsExtensionInfo
, который возвращается новым свойством IDbContextOptionsExtension.Info
.
Почему
В период между выпусками 2.0 и 3.0 нам пришлось несколько раз добавлять и изменять эти методы. Их выделение в новый абстрактный базовый класс упростит реализацию таких изменений и позволит вносить их без нарушения работы существующих расширений.
Устранение проблем
Обновите расширения, чтобы применить новый шаблон.
Примеры доступны во множестве реализаций IDbContextOptionsExtension
для разных типов расширений в исходном коде EF Core.
Оператор LogQueryPossibleExceptionWithAggregateOperator был переименован
Изменение
RelationalEventId.LogQueryPossibleExceptionWithAggregateOperator
переименован в RelationalEventId.LogQueryPossibleExceptionWithAggregateOperatorWarning
.
Почему
Выравнивает именование этого события предупреждения с другими событиями предупреждения.
Устранение проблем
Используйте новое имя. (Обратите внимание, что номер события не изменен.)
Уточнение API для имен ограничений внешнего ключа
Старое поведение
До EF Core 3.0 имена ограничений внешнего ключа назывались просто именами. Например:
var constraintName = myForeignKey.Name;
Новое поведение
Начиная с EF Core 3.0 имена ограничений внешнего ключа называются "имя ограничения". Например:
var constraintName = myForeignKey.ConstraintName;
Почему
Это изменение обеспечивает согласованность именования в этой области, а также указывает, что это имя ограничения внешнего ключа, а не имя столбца или свойства, для которого определен внешний ключ.
Устранение проблем
Используйте новое имя.
Методы IRelationalDatabaseCreator.HasTables/HasTablesAsync стали общедоступными
Старое поведение
До выпуска версии EF Core 3.0 эти методы были защищены.
Новое поведение
Начиная с EF Core 3.0 эти методы стали общедоступными.
Почему
Эти методы используются EF для определения того, была ли база данных создана пустой. Также они могут оказаться полезными при работе с другими решениями (не EF) для определения того, следует ли применять миграцию.
Устранение проблем
Измените уровень доступа для всех переопределений.
Microsoft.EntityFrameworkCore.Design теперь является пакетом DevelopmentDependency
Старое поведение
До выпуска 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
Старое поведение
Пакет 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
Старое поведение
Пространственные пакеты раньше зависели от версии NetTopologySuite 1.15.1.
Новое поведение
Мы обновили пакет, и теперь он зависит от версии 2.0.0.
Почему
Версия NetTopologySuite 2.0.0 помогает решить ряд проблем с удобством использования, возникающих у пользователей EF Core.
Устранение проблем
NetTopologySuite версии 2.0.0 включает некоторые критические изменения. Подробные сведения см. в заметках о выпуске.
Вместо System.Data.SqlClient используется Microsoft.Data.SqlClient
Старое поведение
Пакет 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 двух пакетов очень хорошо совместимы, достаточно изменить пакет и пространство имен.
Множество неоднозначных связей со ссылкой на себя теперь требуют настройки
Старое поведение
Тип сущности с множеством однонаправленных свойств навигации, ссылающихся на себя, и с соответствующими внешними ключами некорректно настраивался как единая связь. Например:
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 в виде нулевой или пустой строки выполняет настройку для включения в схему по умолчанию для модели
Старое поведение
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 отменено
Версия EF Core 3.0 была больше предназначена для .NET Standard 2.1. Это критическое изменение, которое исключало поддержку приложений .NET Framework. Это изменение отменено в EF Core 3.1, и EF Core снова ориентируется на .NET Standard 2.0.
Выполнение запроса с ведением журнала на уровне отладки Debug отменено
Причиной послужило то, что новая конфигурация 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)));