Training
Learning path
Implement finance and operations apps - Training
Plan and design your project methodology to successfully implement finance and operations apps with FastTrack services, data management and more.
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
The following API and behavior changes have the potential to break existing applications updating to EF Core 5.0.0.
EF Core 3.1 targets .NET Standard 2.0, which is supported by .NET Framework.
EF Core 5.0 targets .NET Standard 2.1, which is not supported by .NET Framework. This means EF Core 5.0 cannot be used with .NET Framework applications.
This is part of the wider movement across .NET teams aimed at unification to a single .NET target framework. For more information see the future of .NET Standard.
.NET Framework applications can continue to use EF Core 3.1, which is a long-term support (LTS) release. Alternately, applications can be updated to use .NET Core 3.1 or .NET 5, both of which support .NET Standard 2.1.
GetColumnName()
returned the name of the column that a property is mapped to.
GetColumnName()
still returns the name of a column that a property is mapped to, but this behavior is now ambiguous since EF Core 5 supports TPT and simultaneous mapping to a view or a function where these mappings could use different column names for the same property.
We marked this method as obsolete to guide users to a more accurate overload - GetColumnName(IProperty, StoreObjectIdentifier).
If the entity type is only ever mapped to a single table, and never to views, functions, or multiple tables, the GetColumnBaseName(IReadOnlyProperty) can be used in EF Core 5.0 and 6.0 to obtain the table name. For example:
var columnName = property.GetColumnBaseName();
In EF Core 7.0, this can again be replaced with the new GetColumnName
, which behaves as the original did for simple, single table only mappings.
If the entity type may be mapped to views, functions, or multiple tables, then a StoreObjectIdentifier must be obtained to identity the table, view, or function. This can be then be used to get the column name for that store object. For example:
var columnName = property.GetColumnName(StoreObjectIdentifier.Table("Users", null)));
EF Core did not normally set precision and scale on SqlParameter objects. This means the full precision and scale was sent to SQL Server, at which point SQL Server would round based on the precision and scale of the database column.
EF Core now sets precision and scale on parameters using the values configured for properties in the EF Core model. This means rounding now happens in SqlClient. Consequentially, if the configured precision and scale do not match the database precision and scale, then the rounding seen may change.
Newer SQL Server features, including Always Encrypted, require that parameter facets are fully specified. In addition, SqlClient made a change to round instead of truncate decimal values, thereby matching the SQL Server behavior. This made it possible for EF Core to set these facets without changing the behavior for correctly configured decimals.
Map your decimal properties using a type name that includes precision and scale. For example:
public class Blog
{
public int Id { get; set; }
[Column(TypeName = "decimal(16, 5)")]
public decimal Score { get; set; }
}
Or use HasPrecision
in the model building APIs. For example:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property(e => e.Score).HasPrecision(16, 5);
}
Only the navigations to principal could be configured as required. Therefore, using RequiredAttribute
on the navigation to the dependent (the entity containing the foreign key) or marking it as non-nullable would instead create the foreign key on the defining entity type.
With the added support for required dependents, it is now possible to mark any reference navigation as required, meaning that in the case shown above the foreign key will be defined on the other side of the relationship and the properties won't be marked as required.
Calling IsRequired
before specifying the dependent end is now ambiguous:
modelBuilder.Entity<Blog>()
.HasOne(b => b.BlogImage)
.WithOne(i => i.Blog)
.IsRequired()
.HasForeignKey<BlogImage>(b => b.BlogForeignKey);
The new behavior is necessary to enable support for required dependents (see #12100).
Remove RequiredAttribute
from the navigation to the dependent and place it instead on the navigation to the principal or configure the relationship in OnModelCreating
:
modelBuilder.Entity<Blog>()
.HasOne(b => b.BlogImage)
.WithOne(i => i.Blog)
.HasForeignKey<BlogImage>(b => b.BlogForeignKey)
.IsRequired();
Entity types were mapped to defining queries at the Core level. Anytime the entity type was used in the query root of the entity type was replaced by the defining query for any provider.
APIs for defining query are deprecated. New provider-specific APIs were introduced.
While defining queries were implemented as replacement query whenever query root is used in the query, it had a few issues:
new { ... }
in Select
method, then identifying that as an entity required additional work and made it inconsistent with how EF Core treats nominal types in the query.FromSql
is still needed to pass the SQL string in LINQ expression form.Initially defining queries were introduced as client-side views to be used with In-Memory provider for keyless entities (similar to database views in relational databases). Such definition makes it easy to test application against in-memory database. Afterwards they became broadly applicable, which was useful but brought inconsistent and hard to understand behavior. So we decided to simplify the concept. We made LINQ based defining query exclusive to In-Memory provider and treat them differently. For more information, see this issue.
For relational providers, use ToSqlQuery
method in OnModelCreating
and pass in a SQL string to use for the entity type.
For the In-Memory provider, use ToInMemoryQuery
method in OnModelCreating
and pass in a LINQ query to use for the entity type.
In EF Core 3.1, reference navigations eagerly initialized to non-null values would sometimes be overwritten by entity instances from the database, regardless of whether or not key values matched. However, in other cases, EF Core 3.1 would do the opposite and leave the existing non-null value.
Starting with EF Core 5.0, non-null reference navigations are never overwritten by instances returned from a query.
Note that eager initialization of a collection navigation to an empty collection is still supported.
Initialization of a reference navigation property to an "empty" entity instance results in an ambiguous state. For example:
public class Blog
{
public int Id { get; set; }
public Author Author { get; set; ) = new Author();
}
Normally a query for Blogs and Authors will first create Blog
instances and then set the appropriate Author
instances based on the data returned from the database. However, in this case every Blog.Author
property is already initialized to an empty Author
. Except EF Core has no way to know that this instance is "empty". So overwriting this instance could potentially silently throw away a valid Author
. Therefore, EF Core 5.0 now consistently does not overwrite a navigation that is already initialized.
This new behavior also aligns with the behavior of EF6 in most cases, although upon investigation we also found some cases of inconsistency in EF6.
If this break is encountered, then the fix is to stop eagerly initializing reference navigation properties.
Calling ToView(string)
made the migrations ignore the entity type in addition to mapping it to a view.
Now ToView(string)
marks the entity type as not mapped to a table in addition to mapping it to a view. This results in the first migration after upgrading to EF Core 5 to try to drop the default table for this entity type as it's not longer ignored.
EF Core now allows an entity type to be mapped to both a table and a view simultaneously, so ToView
is no longer a valid indicator that it should be ignored by migrations.
Use the following code to mark the mapped table as excluded from migrations:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("UserView", t => t.ExcludeFromMigrations());
}
ToTable(null)
would reset the table name to the default.
ToTable(null)
now marks the entity type as not mapped to any table.
EF Core now allows an entity type to be mapped to both a table and a view simultaneously, so ToTable(null)
is used to indicate that it isn't mapped to any table.
Use the following code to reset the table name to the default if it's not mapped to a view or a DbFunction:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Metadata.RemoveAnnotation(RelationalAnnotationNames.TableName);
}
HasGeometricDimension was used to enable additional dimensions (Z and M) on geometry columns. However, it only ever affected database creation. It was unnecessary to specify it to query values with additional dimensions. It also didn't work correctly when inserting or updating values with additional dimensions (see #14257).
To enable inserting and updating geometry values with additional dimensions (Z and M), the dimension needs to be specified as part of the column type name. This API matches more closely to the underlying behavior of SpatiaLite's AddGeometryColumn function.
Using HasGeometricDimension after specifying the dimension in the column type is unnecessary and redundant, so we removed HasGeometricDimension entirely.
Use HasColumnType
to specify the dimension:
modelBuilder.Entity<GeoEntity>(
x =>
{
// Allow any GEOMETRY value with optional Z and M values
x.Property(e => e.Geometry).HasColumnType("GEOMETRYZM");
// Allow only POINT values with an optional Z value
x.Property(e => e.Point).HasColumnType("POINTZ");
});
The partition key property was only added to the alternate key that includes id
.
The partition key property is now also added to the primary key by convention.
This change makes the model better aligned with Azure Cosmos DB semantics and improves the performance of Find
and some queries.
To prevent the partition key property to be added to the primary key, configure it in OnModelCreating
.
modelBuilder.Entity<Blog>()
.HasKey(b => b.Id);
The shadow property mapped to the id
JSON property was also named id
.
The shadow property created by convention is now named __id
.
This change makes it less likely that the id
property clashes with an existing property on the entity type.
To go back to the 3.x behavior, configure the id
property in OnModelCreating
.
modelBuilder.Entity<Blog>()
.Property<string>("id")
.ToJsonProperty("id");
Properties of type byte[] were stored as a number array.
Properties of type byte[] are now stored as a base64 string.
This representation of byte[] aligns better with expectations and is the default behavior of the major JSON serialization libraries.
Existing data stored as number arrays will still be queried correctly, but currently there isn't a supported way to change back the insert behavior. If this limitation is blocking your scenario, comment on this issue
Previously the extension methods were called GetPropertyName
and SetPropertyName
The old API was removed and new methods added: GetJsonPropertyName
, SetJsonPropertyName
This change removes the ambiguity around what these methods are configuring.
Use the new API.
Value generators were only called when the entity state changed to Added.
Value generators are now called when the entity state is changed from Detached to Unchanged, Updated, or Deleted and the property contains the default values.
This change was necessary to improve the experience with properties that are not persisted to the data store and have their value generated always on the client.
To prevent the value generator from being called, assign a non-default value to the property before the state is changed.
IMigrationsModelDiffer
API was defined using IModel
.
IMigrationsModelDiffer
API now uses IRelationalModel
. However the model snapshot still contains only IModel
as this code is part of the application and Entity Framework can't change it without making a bigger breaking change.
IRelationalModel
is a newly added representation of the database schema. Using it to find differences is faster and more accurate.
Use the following code to compare the model from snapshot
with the model from context
:
var dependencies = context.GetService<ProviderConventionSetBuilderDependencies>();
var relationalDependencies = context.GetService<RelationalConventionSetBuilderDependencies>();
var typeMappingConvention = new TypeMappingConvention(dependencies);
typeMappingConvention.ProcessModelFinalizing(((IConventionModel)modelSnapshot.Model).Builder, null);
var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies);
var sourceModel = relationalModelConvention.ProcessModelFinalized(snapshot.Model);
var modelDiffer = context.GetService<IMigrationsModelDiffer>();
var hasDifferences = modelDiffer.HasDifferences(
((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel(),
context.Model.GetRelationalModel());
We are planning to improve this experience in 6.0 (see #22031)
It was possible to change the discriminator value before calling SaveChanges
An exception will be thrown in the above case.
EF doesn't expect the entity type to change while it is still being tracked, so changing the discriminator value leaves the context in an inconsistent state, which might result in unexpected behavior.
If changing the discriminator value is necessary and the context will be disposed immediately after calling SaveChanges
, the discriminator can be made mutable:
modelBuilder.Entity<BaseEntity>()
.Property<string>("Discriminator")
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
Provider-specific EF.Functions methods contained implementation for client execution, which allowed them to be executed on the in-memory provider. For example, EF.Functions.DateDiffDay
is a Sql Server specific method, which worked on InMemory provider.
Provider-specific methods have been updated to throw an exception in their method body to block evaluating them on client side.
Provider-specific methods map to a database function. The computation done by the mapped database function can't always be replicated on the client side in LINQ. It may cause the result from the server to differ when executing the same method on client. Since these methods are used in LINQ to translate to specific database functions, they don't need to be evaluated on client side. As the in-memory provider is a different database, these methods aren't available for this provider. Trying to execute them for InMemory provider, or any other provider that doesn't translate these methods, throws an exception.
Since there's no way to mimic behavior of database functions accurately, you should test the queries containing them against same kind of database as in production.
Previously, only one index could be defined over a given set of properties. The database name of an index was configured using IndexBuilder.HasName.
Multiple indexes are now allowed on the same set or properties. These indexes are now distinguished by a name in the model. By convention, the model name is used as the database name; however it can also be configured independently using HasDatabaseName.
In the future, we'd like to enable both ascending and descending indexes or indexes with different collations on the same set of properties. This change moves us another step in that direction.
Any code that was previously calling IndexBuilder.HasName should be updated to call HasDatabaseName instead.
If your project includes migrations generated prior to EF Core version 2.0.0, you can safely ignore the warning in those files and suppress it by adding #pragma warning disable 612, 618
.
Previously, you had to install a separate pluralizer package in order to pluralize DbSet and collection navigation names and singularize table names when scaffolding a DbContext and entity types by reverse engineering a database schema.
EF Core now includes a pluralizer that uses the Humanizer library. This is the same library Visual Studio uses to recommend variable names.
Using plural forms of words for collection properties and singular forms for types and reference properties is idiomatic in .NET.
To disable the pluralizer, use the --no-pluralize
option on dotnet ef dbcontext scaffold
or the -NoPluralize
switch on Scaffold-DbContext
.
EF Core prior to 5.0 supported only one form of navigation property, represented by the INavigation
interface.
EF Core 5.0 introduces many-to-many relationships which use "skip navigations". These are represented by the ISkipNavigation
interface, and most of the functionality of INavigation
has been pushed down to a common base interface: INavigationBase
.
Most of the functionality between normal and skip navigations is the same. However, skip navigations have a different relationship to foreign keys than normal navigations, since the FKs involved are not directly on either end of the relationship, but rather in the join entity.
In many cases applications can switch to using the new base interface with no other changes. However, in cases where the navigation is used to access foreign key properties, application code should either be constrained to only normal navigations, or updated to do the appropriate thing for both normal and skip navigations.
Old behavior
Previously, queries involving correlated collections followed by GroupBy
, as well as some queries using Distinct
we allowed to execute.
GroupBy example:
context.Parents
.Select(p => p.Children
.GroupBy(c => c.School)
.Select(g => g.Key))
Distinct
example - specifically Distinct
queries where inner collection projection doesn't contain the primary key:
context.Parents
.Select(p => p.Children
.Select(c => c.School)
.Distinct())
These queries could return incorrect results if the inner collection contained any duplicates, but worked correctly if all the elements in the inner collection were unique.
New behavior
These queries are no longer supported. Exception is thrown indicating that we don't have enough information to correctly build the results.
Why
For correlated collection scenarios we need to know entity's primary key in order to assign collection entities to the correct parent. When inner collection doesn't use GroupBy
or Distinct
, the missing primary key can simply be added to the projection. However in case of GroupBy
and Distinct
it can't be done because it would change the result of GroupBy
or Distinct
operation.
Mitigations
Rewrite the query to not use GroupBy
or Distinct
operations on the inner collection, and perform these operations on the client instead.
context.Parents
.Select(p => p.Children.Select(c => c.School))
.ToList()
.Select(x => x.GroupBy(c => c).Select(g => g.Key))
context.Parents
.Select(p => p.Children.Select(c => c.School))
.ToList()
.Select(x => x.Distinct())
Old behavior
Previously, it was possible to use collection of a Queryable type inside the projection in some cases, for example as an argument to a List<T>
constructor:
context.Blogs
.Select(b => new List<Post>(context.Posts.Where(p => p.BlogId == b.Id)))
New behavior
These queries are no longer supported. Exception is thrown indicating that we can't create an object of Queryable type and suggesting how this could be fixed.
Why
We can't materialize an object of a Queryable type, so they would automatically be created using List<T>
type instead. This would often cause an exception due to type mismatch which was not very clear and could be surprising to some users. We decided to recognize the pattern and throw a more meaningful exception.
Mitigations
Add ToList()
call after the Queryable object in the projection:
context.Blogs.Select(b => context.Posts.Where(p => p.BlogId == b.Id).ToList())
.NET feedback
.NET is an open source project. Select a link to provide feedback:
Training
Learning path
Implement finance and operations apps - Training
Plan and design your project methodology to successfully implement finance and operations apps with FastTrack services, data management and more.