In an attempt to correct many perceived deficiencies in Entity Framework Core, Microsoft is introducing 40 breaking changes to EF Core 3. You can see the entire breaking changes list on Microsoft Docs, but here are some of the highlights.
Client-Side Query Execution
In order to work around the limitations of EF Core’s SQL generator, it defaulted to partially executing queries client side. This means for LINQ queries that it couldn’t translate into SQL; it would simply download the tables from the database and perform the remaining operations in memory. Until version 2.1, even GROUP BY was performed client-side.
The downside of this behavior is that one problem in a Where()
clause could cause EF Core to literally download the entire table. Developers have also reported that in cases where it is unable to generate a correlated subquery, it will instead perform hundreds or thousands of secondary queries.
Under the new default, only the final Select()
operation may be performed client-side. If EF Core can’t generate the correct SQL it will instead throw an exception. Developers may override this behavior, but Microsoft would prefer you submit a bug request instead.
You can read more about this change in the ticket called Outline guiding principles and decide on direction for queries in 3.0.
Parameterized and Interpolated SQL
As we reported in 2017, EF Core’s string interpolation feature raised many concerns. With this feature, interpolated strings can be automatically converted into parameterized SQL, but only if they are not stored in a temporary variable first.
v1 = context.Customers.FromSql($"SELECT * FROM Customers WHERE City = {city}");
var sql = $"SELECT * FROM Customers WHERE City = {city}";
v2 = context.Customers.FromSql(sql);
In the example above, the v1 is correctly parameterized, while v2 exposes a SQL injection vulnerability.
To remove this stumbling block, the FromSql
function is being removed. It will be replaced with FromSqlRaw
and FromSqlInterpolated
in order to make the developer’s intention clear.
Temporary Keys
In order to track new entities, EF Core would often create temporary primary keys. These would be stored in the normal key property (e.g. CustomerKey or OrderId) as a negative number. In theory, these would be replaced by the real keys when they are generated. Unfortunately, this has several negative side-effects, such as those fake keys being displayed in the UI or even being saved to the database.
EF Core 3 will move this tracking information into entity's tracking information, leaving the key property with only clean data.
Cascade Delete Timing
Cascading deletes will occur immediately when methods such as context.Remove()
are called. Previously, EF Core wouldn’t calculate which child records were being deleted until SaveChanges
is called, making it hard to predict what exactly would happen. This change mostly affects code that logs which records are about to be modified/deleted.
You can restore the old behavior by setting the CascadeDeleteTiming
and DeleteOrphansTiming
options to CascadeTiming.OnSaveChanges
.
Query Types are Obsolete
Unlike previous versions of Entity Framework, EF Core was designed to only work with tables that expose a primary key. This is problematic because the results of a view or stored procedure don’t have primary keys. So in EF Core 2.1, they introduced the concept of query types.
Essentially, query types use a parallel object model. Instead of DbSet<T>
, the developer defines them using a DbQuery<T>
. They are registered using ModelBuilder.Query<>()
instead of ModelBuilder.Entity<>()
and are invoked using DbContext.Query<>()
instead of DbContext.Set<>()
.
Many developers complained about the unnecessary confusion between the two, so they have been removed. Starting in EF Core 3, developers are expected to use the normal DbSet model for all data sources. If there is no primary key, the developer simply annotates them with .HasNoKey()
when registering the entity.
Property Getters and Setters are Ignored
In the past, EF Core would invoke a property getter or setter except when materializing the results of a query. In the query case, it would bypass the property and write directly to the underlying field if known.
With this change, the underlying backing field will always be used if known, regardless of why EF Core is interacting with it. The upside is this prevents business logic from being accidentally triggered.
The downside being business logic, such updating calculated fields, are not triggered. So, you may need to modify the UsePropertyAccessMode
setting to get the behavior you wanted.
Backing Field Detection
When detecting the backing field for the above change, there are times when the code is ambiguous. In the past EF Core would just guess which field should be set based on an internal ranking system.
In EF Core 3, any ambiguity here will cause an exception to be thrown. The developer then has to manually indicate which field to use in the model builder.
ValueTask Replaces Task
Making Task an object was considered to be one of the biggest mistakes in .NET. While acceptable for long-running tasks, it often creates excessive memory pressure when a lot of short-lived tasks are created. Thus, the introduction of ValueTask
, a struct based alternative.
In support of this new type, several methods such as FindAsync and NextValueAsync have been updated to return a ValueTask
instead of a Task
. This won’t affect code that merely awaits the result, but if you do anything else with the task you may need to call AsTask()
to change it from a ValueTask
to a Task
.
IEntityType and IProperty Simplification
These interfaces are dropping five methods between them, to be replaced with extension methods. The justification is it makes it easier to implement the interface if they are smaller.