title | description | author | ms.date | uid |
---|---|---|---|---|
Breaking changes in EF Core 6.0 - EF Core |
Complete list of breaking changes introduced in Entity Framework Core 6.0 |
ajcvickers |
09/21/2022 |
core/what-is-new/ef-core-6.0/breaking-changes |
The following API and behavior changes have the potential to break existing applications updating to EF Core 6.0.
EF Core 6.0 targets .NET 6. Applications targeting older .NET, .NET Core, and .NET Framework versions will need to target .NET 6 to use EF Core 6.0.
* These changes are of particular interest to authors of database providers and extensions.
Models with nested optional dependents sharing a table and with no required properties were allowed, but could result in data loss when querying for the data and then saving again. For example, consider the following model:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public ContactInfo ContactInfo { get; set; }
}
[Owned]
public class ContactInfo
{
public string Phone { get; set; }
public Address Address { get; set; }
}
[Owned]
public class Address
{
public string House { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string Postcode { get; set; }
}
None of the properties in ContactInfo
or Address
are required, and all these entity types are mapped to the same table. The rules for optional dependents (as opposed to required dependents) say that if all of the columns for ContactInfo
are null, then no instance of ContactInfo
will be created when querying for the owner Customer
. However, this also means that no instance of Address
will be created, even if the Address
columns are non-null.
Attempting to use this model will now throw the following exception:
System.InvalidOperationException: Entity type 'ContactInfo' is an optional dependent using table sharing and containing other dependents without any required non shared property to identify whether the entity exists. If all nullable properties contain a null value in database then an object instance won't be created in the query causing nested dependent's values to be lost. Add a required property to create instances with null values for other properties or mark the incoming navigation as required to always create an instance.
This prevents data loss when querying and saving data.
Using models with nested optional dependents sharing a table and with no required properties often resulted in silent data loss.
Avoid using optional dependents sharing a table and with no required properties. There are three easy ways to do this:
-
Make the dependents required. This means that the dependent entity will always have a value after it is queried, even if all its properties are null. For example:
public class Customer { public int Id { get; set; } public string Name { get; set; } [Required] public Address Address { get; set; } }
Or:
modelBuilder.Entity<Customer>( b => { b.OwnsOne(e => e.Address); b.Navigation(e => e.Address).IsRequired(); });
-
Make sure that the dependent contains at least one required property.
-
Map optional dependents to their own table, instead of sharing a table with the principal. For example:
modelBuilder.Entity<Customer>( b => { b.ToTable("Customers"); b.OwnsOne(e => e.Address, b => b.ToTable("CustomerAddresses")); });
The problems with optional dependents and examples of these mitigations are included in the documentation for What's new in EF Core 6.0.
It was possible to reassign an owned entity to a different owner entity.
This action will now throw an exception:
The property '{entityType}.{property}' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key, first delete the dependent and invoke 'SaveChanges', and then associate the dependent with the new principal.
Even though we don't require key properties to exist on an owned type, EF will still create shadow properties to be used as the primary key and the foreign key pointing to the owner. When the owner entity is changed it causes the values of the foreign key on the owned entity to change, and since they are also used as the primary key this results in the entity identity to change. This isn't yet fully supported in EF Core and was only conditionally allowed for owned entities, sometimes resulting in the internal state becoming inconsistent.
Instead of assigning the same owned instance to a new owner you can assign a copy and delete the old one.
Tracking Issue #24803 What's new: Default to implicit ownership
As in other providers, related entity types were discovered as normal (non-owned) types.
Related entity types will now be owned by the entity type on which they were discovered. Only the entity types that correspond to a xref:Microsoft.EntityFrameworkCore.DbSet%601 property will be discovered as non-owned.
This behavior follows the common pattern of modeling data in Azure Cosmos DB of embedding related data into a single document. Azure Cosmos DB does not natively support joining different documents, so modeling related entities as non-owned has limited usefulness.
To configure an entity type to be non-owned call modelBuilder.Entity<MyEntity>();
Tracking Issue #13837 What's new: Default to implicit ownership
Previously, connections in Microsoft.Data.Sqlite were not pooled.
Starting in 6.0, connections are now pooled by default. This results in database files being kept open by the process even after the ADO.NET connection object is closed.
Pooling the underlying connections greatly improves the performance of opening and closing ADO.NET connection objects. This is especially noticeable for scenarios where opening the underlying connection is expensive as in the case of encryption, or in scenarios where there are a lot of short-lived connection to the database.
Connection pooling can be disabled by adding Pooling=False
to a connection string.
Some scenarios (like deleting the database file) may now encounter errors stating that the file is still in use. You can manually clear the connection pool before performing operations of the file using SqliteConnection.ClearPool()
.
SqliteConnection.ClearPool(connection);
File.Delete(databaseFile);
Scaffolding (reverse engineering) a DbContext
and entity types from an existing database always explicitly mapped join tables to join entity types for many-to-many relationships.
Simple join tables containing only two foreign key properties to other tables are no longer mapped to explicit entity types, but are instead mapped as a many-to-many relationship between the two joined tables.
Many-to-many relationships without explicit join types were introduced in EF Core 5.0 and are a cleaner, more natural way to represent simple join tables.
There are two mitigations. The preferred approach is to update code to use the many-to-many relationships directly. It is very rare that the join entity type needs to be used directly when it contains only two foreign keys for the many-to-many relationships.
Alternately, the explicit join entity can be added back to the EF model. For example, assuming a many-to-many relationship between Post
and Tag
, add back the join type and navigations using partial classes:
public partial class PostTag
{
public int PostsId { get; set; }
public int TagsId { get; set; }
public virtual Post Posts { get; set; }
public virtual Tag Tags { get; set; }
}
public partial class Post
{
public virtual ICollection<PostTag> PostTags { get; set; }
}
public partial class Tag
{
public virtual ICollection<PostTag> PostTags { get; set; }
}
Then add configuration for the join type and navigations to a partial class for the DbContext:
public partial class DailyContext
{
partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>(entity =>
{
entity.HasMany(d => d.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
l => l.HasOne<Tag>(e => e.Tags).WithMany(e => e.PostTags).HasForeignKey(e => e.TagsId),
r => r.HasOne<Post>(e => e.Posts).WithMany(e => e.PostTags).HasForeignKey(e => e.PostsId),
j =>
{
j.HasKey("PostsId", "TagsId");
j.ToTable("PostTag");
});
});
}
}
Finally, remove the generated configuration for the many-to-many relationship from the scaffolded context. This is needed because the scaffolded join entity type must be removed from the model before the explicit type can be used. This code will need to be removed each time the context is scaffolded, but because the code above is in partial classes it will persist.
Note that with this configuration, the join entity can be used explicitly, just like in previous versions of EF Core. However, the relationship can also be used as a many-to-many relationship. This means that updating the code like this can be a temporary solution while the rest of the code is updated to use the relationship as a many-to-many in the natural way.
Some of the mappings between a relationship's OnDelete()
behavior and the foreign keys' ON DELETE
behavior in the database were inconsistent in both Migrations and Scaffolding.
The following table illustrates the changes for Migrations.
OnDelete() | ON DELETE |
---|---|
NoAction | NO ACTION |
ClientNoAction | NO ACTION |
Restrict | RESTRICT |
Cascasde | CASCADE |
ClientCascade | |
SetNull | SET NULL |
ClientSetNull |
The changes for Scaffolding are as follows.
ON DELETE | OnDelete() |
---|---|
NO ACTION | ClientSetNull |
RESTRICT | |
CASCADE | Cascade |
SET NULL | SetNull |
The new mappings are more consistent. The default database behavior of NO ACTION is now preferred over the more restrictive and less performant RESTRICT behavior.
The default OnDelete() behavior of optional relationships is ClientSetNull. Its mapping has changed from RESTRICT to NO ACTION. This may cause a lot of operations to be generated in your first migration added after upgrading to EF Core 6.0.
You can choose to either apply these operations or manually remove them from the migration since they have no functional impact on EF Core.
SQL Server doesn't support RESTRICT, so these foreign keys were already created using NO ACTION. The migration operations will have no affect on SQL Server and are safe to remove.
The in-memory database allowed saving null values even when the property was configured as required.
The in-memory database throws a Microsoft.EntityFrameworkCore.DbUpdateException
when SaveChanges
or SaveChangesAsync
is called and a required property is set to null.
The in-memory database behavior now matches the behavior of other databases.
The previous behavior (i.e. not checking null values) can be restored when configuring the in-memory provider. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseInMemoryDatabase("MyDatabase", b => b.EnableNullChecks(false));
}
When performing SQL JOINs on collections (one-to-many relationships), EF Core used to add an ORDER BY for each key column of the joined table. For example, loading all Blogs with their related Posts was done via the following SQL:
SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]
These orderings are necessary for proper materialization of the entities.
The very last ORDER BY for a collection join is now omitted:
SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]
An ORDER BY for the Post's ID column is no longer generated.
Every ORDER BY imposes additional work at the database side, and the last ordering isn't necessary for EF Core's materialization needs. Data shows that removing this last ordering can produce a significant performance improvement in some scenarios.
If your application expects joined entities to be returned in a particular order, make that explicit by adding a LINQ OrderBy
operator to your query.
xref:Microsoft.EntityFrameworkCore.DbSet%601, which is used to execute queries on DbContext, used to implement xref:System.Collections.Generic.IAsyncEnumerable%601.
xref:Microsoft.EntityFrameworkCore.DbSet%601 no longer directly implements xref:System.Collections.Generic.IAsyncEnumerable%601.
xref:Microsoft.EntityFrameworkCore.DbSet%601 was originally made to implement xref:System.Collections.Generic.IAsyncEnumerable%601 mainly in order to allow direct enumeration on it via the foreach
construct. Unfortunately, when a project also references System.Linq.Async in order to compose async LINQ operators client-side, this resulted in an ambiguous invocation error between the operators defined over IQueryable<T>
and those defined over IAsyncEnumerable<T>
. C# 9 added extension GetEnumerator
support for foreach
loops, removing the original main reason to reference IAsyncEnumerable
.
The vast majority of DbSet
usages will continue to work as-is, since they compose LINQ operators over DbSet
, enumerate it, etc. The only usages broken are those which attempt to cast DbSet
directly to IAsyncEnumerable
.
If you need to refer to a xref:Microsoft.EntityFrameworkCore.DbSet%601 as an xref:System.Collections.Generic.IAsyncEnumerable%601, call xref:Microsoft.EntityFrameworkCore.DbSet%601.AsAsyncEnumerable%2A?displayProperty=nameWithType to explicitly cast it.
An entity type was not mapped to a table by default when used as a return type of a TVF configured with xref:Microsoft.EntityFrameworkCore.RelationalModelBuilderExtensions.HasDbFunction%2A.
An entity type used as a return type of a TVF retains the default table mapping.
It isn't intuitive that configuring a TVF removes the default table mapping for the return entity type.
To remove the default table mapping, call xref:Microsoft.EntityFrameworkCore.RelationalEntityTypeBuilderExtensions.ToTable(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder,System.String):
modelBuilder.Entity<MyEntity>().ToTable((string?)null));
Check constraints with the same name were allowed to be declared and used on the same table.
Explicitly configuring two check constraints with the same name on the same table will now result in an exception. Check constraints created by a convention will be assigned a unique name.
Most databases don't allow two check constraints with the same name to be created on the same table, and some require them to be unique even across tables. This would result in exception being thrown when applying a migration.
In some cases, valid check constraint names might be different due to this change. To specify the desired name explicitly, call xref:Microsoft.EntityFrameworkCore.Metadata.Builders.CheckConstraintBuilder.HasName%2A:
modelBuilder.Entity<MyEntity>().HasCheckConstraint("CK_Id", "Id > 0", c => c.HasName("CK_MyEntity_Id"));
There were three sets of metadata interfaces: xref:Microsoft.EntityFrameworkCore.Metadata.IModel, xref:Microsoft.EntityFrameworkCore.Metadata.IMutableModel and xref:Microsoft.EntityFrameworkCore.Metadata.IConventionModel as well as extension methods.
A new set of IReadOnly
interfaces has been added, e.g. xref:Microsoft.EntityFrameworkCore.Metadata.IReadOnlyModel. Extension methods that were previously defined for the metadata interfaces have been converted to default interface methods.
Default interface methods allow the implementation to be overridden, this is leveraged by the new run-time model implementation to offer better performance.
These changes shouldn't affect most code. However, if you were using the extension methods via the static invocation syntax, it would need to be converted to instance invocation syntax.
xref:Microsoft.EntityFrameworkCore.Storage.IExecutionStrategy is now a singleton service. This means that any added state in custom implementations will remain between executions and the delegate passed to xref:Microsoft.EntityFrameworkCore.Infrastructure.RelationalDbContextOptionsBuilder%602.ExecutionStrategy%2A will only be executed once.
This reduced allocations on two hot paths in EF.
Implementations deriving from xref:Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy should clear any state in xref:Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.OnFirstExecution.
Conditional logic in the delegate passed to xref:Microsoft.EntityFrameworkCore.Infrastructure.RelationalDbContextOptionsBuilder%602.ExecutionStrategy%2A should be moved to a custom implementation of xref:Microsoft.EntityFrameworkCore.Storage.IExecutionStrategy.
The errors listed in the issue above are now considered transient. When using the default (non-retrying) execution strategy, these errors will now be wrapped in an addition exception instance.
We continue to gather feedback from both users and SQL Server team on which errors should be considered transient.
To change the set of errors that are considered transient, use a custom execution strategy that could be derived from xref:Microsoft.EntityFrameworkCore.SqlServerRetryingExecutionStrategy - xref:core/miscellaneous/connection-resiliency.
In EF Core 5, only '|'
was escaped in id
values.
In EF Core 6, '/'
, '\'
, '?'
and '#'
are also escaped in id
values.
These characters are invalid, as documented in Resource.Id. Using them in id
will cause queries to fail.
You can override the generated value by setting it before the entity is marked as Added
:
var entry = context.Attach(entity);
entry.Property("__id").CurrentValue = "MyEntity|/\\?#";
entry.State = EntityState.Added;
Many query services and some design-time services that were registered as Singleton
are now registered as Scoped
.
The lifetime had to be changed to allow a new feature - xref:Microsoft.EntityFrameworkCore.ModelConfigurationBuilder.DefaultTypeMapping%2A - to affect queries.
The design-time services lifetimes have been adjusted to match the run-time services lifetimes to avoid errors when using both.
Use xref:Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.TryAdd%2A to register EF Core services using the default lifetime. Only use xref:Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.TryAddProviderSpecificServices%2A for services that are not added by EF.
In EF Core 5, xref:Microsoft.EntityFrameworkCore.Infrastructure.DbContextOptionsExtensionInfo.GetServiceProviderHashCode%2A returned long
and was used directly as part of the cache key for the service provider.
xref:Microsoft.EntityFrameworkCore.Infrastructure.DbContextOptionsExtensionInfo.GetServiceProviderHashCode%2A now returns int
and is only used to calculate the hash code of the cache key for the service provider.
Also, xref:Microsoft.EntityFrameworkCore.Infrastructure.DbContextOptionsExtensionInfo.ShouldUseSameServiceProvider%2A needs to be implemented to indicate whether the current object represents the same service configuration and thus can use the same service provider.
Just using a hash code as part of the cache key resulted in occasional collisions that were hard to diagnose and fix. The additional method ensures that the same service provider is used only when appropriate.
Many extensions don't expose any options that affect registered services and can use the following implementation of xref:Microsoft.EntityFrameworkCore.Infrastructure.DbContextOptionsExtensionInfo.ShouldUseSameServiceProvider%2A:
private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
public ExtensionInfo(IDbContextOptionsExtension extension)
: base(extension)
{
}
...
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
=> other is ExtensionInfo;
}
Otherwise, additional predicates should be added to compare all relevant options.
In EF Core 5, specific conventions needed to be invoked before the snapshot model was ready to be used.
xref:Microsoft.EntityFrameworkCore.Infrastructure.IModelRuntimeInitializer was introduced to hide some of the required steps, and a run-time model was introduced that doesn't have all the migrations metadata, so the design-time model should be used for model diffing.
xref:Microsoft.EntityFrameworkCore.Infrastructure.IModelRuntimeInitializer abstracts away the model finalization steps, so these can now be changed without further breaking changes for the users.
The optimized run-time model was introduced to improve run-time performance. It has several optimizations, one of which is removing metadata that is not used at run-time.
The following snippet illustrates how to check whether the current model is different from the snapshot model:
var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;
if (snapshotModel is IMutableModel mutableModel)
{
snapshotModel = mutableModel.FinalizeModel();
}
if (snapshotModel != null)
{
snapshotModel = context.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
}
var hasDifferences = context.GetService<IMigrationsModelDiffer>().HasDifferences(
snapshotModel?.GetRelationalModel(),
context.GetService<IDesignTimeModel>().Model.GetRelationalModel());
This snippet shows how to implement xref:Microsoft.EntityFrameworkCore.Design.IDesignTimeDbContextFactory%601 by creating a model externally and calling xref:Microsoft.EntityFrameworkCore.DbContextOptionsBuilder.UseModel%2A:
internal class MyDesignContext : IDesignTimeDbContextFactory<MyContext>
{
public TestContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DB"));
var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder();
CustomizeModel(modelBuilder);
var model = modelBuilder.Model.FinalizeModel();
var serviceContext = new MyContext(optionsBuilder.Options);
model = serviceContext.GetService<IModelRuntimeInitializer>().Initialize(model);
return new MyContext(optionsBuilder.Options);
}
}
In EF Core 5, xref:Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder.HasIndex%2A returned IndexBuilder<TEntity>
where TEntity
is the owner type.
xref:Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder.HasIndex%2A now returns IndexBuilder<TDependentEntity>
, where TDependentEntity
is the owned type.
The returned builder object wasn't typed correctly.
Recompiling your assembly against the latest version of EF Core will be enough to fix any issues caused by this change.
In EF Core 5, calling xref:Microsoft.EntityFrameworkCore.Metadata.Builders.DbFunctionBuilder.HasSchema%2A with null
value didn't store the configuration source, thus xref:Microsoft.EntityFrameworkCore.DbFunctionAttribute was able to override it.
Calling xref:Microsoft.EntityFrameworkCore.Metadata.Builders.DbFunctionBuilder.HasSchema%2A with null
value now stores the configuration source and prevents the attribute from overriding it.
Configuration specified with the xref:Microsoft.EntityFrameworkCore.ModelBuilder API should not be overridable by data annotations.
Remove the HasSchema
call to let the attribute configure the schema.
Navigation properties set to an empty object were left unchanged for tracking queries, but were overwritten for non-tracking queries. For example, consider the following entity types:
public class Foo
{
public int Id { get; set; }
public Bar Bar { get; set; } = new(); // Don't do this.
}
public class Bar
{
public int Id { get; set; }
}
A no-tracking query for Foo
including Bar
set Foo.Bar
to the entity queried from the database. For example, this code:
var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");
Printed Foo.Bar.Id = 1
.
However, the same query run for tracking didn't overwrite Foo.Bar
with the entity queried from the database. For example, this code:
var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");
Printed Foo.Bar.Id = 0
.
In EF Core 6.0, the behavior of tracking queries now matches that of no-tracking queries. This means that both this code:
var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");
And this code:
var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");
Print Foo.Bar.Id = 1
.
There are two reasons for making this change:
- To ensure that tracking and no-tracking queries have consistent behavior.
- When a database is queried it is reasonable to assume that the application code wants to get back the values that are stored in the database.
There are two mitigations:
- Do not query for objects from the database that should not be included in the results. For example, in the code snippets above, do not
Include
Foo.Bar
if theBar
instance should not be returned from the database and included in the results. - Set the value of the navigation after querying from the database. For example, in the code snippets above, call
foo.Bar = new()
after running the query.
Also, consider not initializing related entity instances to default objects. This implies that the related instance is a new entity, not saved to the database, with no key value set. If instead the related entity does exist in the database, then the data in code is fundamentally at odds with the data stored in the database.
Enum properties can be mapped to string columns in the database using HasConversion<string>()
or EnumToStringConverter
. This results in EF Core converting string values in the column to matching members of the .NET enum type. However, if the string value did not match and enum member, then the property was set to the default value for the enum.
EF Core 6.0 now throws an InvalidOperationException
with the message "Cannot convert string value '{value}
' from the database to any value in the mapped '{enumType}
' enum."
Converting to the default value can result in database corruption if the entity is later saved back to the database.
Ideally, ensure that the database column only contains valid values. Alternately, implement a ValueConverter
with the old behavior.
DbFunctionBuilder.HasTranslation now provides the function arguments as IReadOnlyList rather than IReadOnlyCollection
When configuring translation for a user-defined function using HasTranslation
method, the arguments to the function were provided as IReadOnlyCollection<SqlExpression>
.
In EF Core 6.0, the arguments are now provided as IReadOnlyList<SqlExpression>
.
IReadOnlyList
allows to use indexers, so the arguments are now easier to access.
None. IReadOnlyList
implements IReadOnlyCollection
interface, so the transition should be straightforward.
When an entity was mapped to a table-valued function, its default mapping to a table was removed.
In EF Core 6.0, the entity is still mapped to a table using default mapping, even if it's also mapped to table-valued function.
Table-valued functions which return entities are often used either as a helper or to encapsulate an operation returning a collection of entities, rather than as a strict replacement of the entire table. This change aims to be more in line with the likely user intention.
Mapping to a table can be explicitly disabled in the model configuration:
modelBuilder.Entity<MyEntity>().ToTable((string)null);
The dotnet-ef command has targeted .NET Core 3.1 for a while now. This allowed you to use newer version of the tool without installing newer versions of the .NET runtime.
In EF Core 6.0.6, the dotnet-ef tool now targets .NET 6. You can still use the tool on projects targeting older versions of .NET and .NET Core, but you'll need to install the .NET 6 runtime in order to run the tool.
The .NET 6.0.200 SDK updated the behavior of dotnet tool install
on osx-arm64 to create an osx-x64 shim for tools targeting .NET Core 3.1. In order to maintain a working default experience for dotnet-ef, we had to update it to target .NET 6.
To run dotnet-ef without installing the .NET 6 runtime, you can install an older version of the tool:
dotnet tool install dotnet-ef --version 3.1.*
IModelCacheKeyFactory
did not have an option to cache the design-time model separately from the runtime model.
IModelCacheKeyFactory
has a new overload that allows the design-time model to be cached separately from the runtime model. Not implementing this method may result in an exception similar to:
System.InvalidOperationException: 'The requested configuration is not stored in the read-optimized model, please use 'DbContext.GetService<IDesignTimeModel>().Model'.'
Implementation of compiled models required separation of the design-time (used when building the model) and runtime (used when executing queries, etc.) models. If the runtime code needs access to design-time information, then the design-time model must be cached.
Implement the new overload. For example:
public object Create(DbContext context, bool designTime)
=> context is DynamicContext dynamicContext
? (context.GetType(), dynamicContext.UseIntProperty, designTime)
: (object)context.GetType();
The navigation '{navigation}' was ignored from 'Include' in the query since the fix-up will automatically populate it. If any further navigations are specified in 'Include' afterwards then they will be ignored. Walking back in the include tree is not allowed.
The event CoreEventId.NavigationBaseIncludeIgnored
was logged as a warning by default.
The event CoreEventId.NavigationBaseIncludeIgnored
was logged as an error by default and causes an exception to be thrown.
These query patterns are not allowed, so EF Core now throws to indicate that the queries should be updated.
The old behavior can be restored by configuring the event as a warning. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.ConfigureWarnings(b => b.Warn(CoreEventId.NavigationBaseIncludeIgnored));