Announcing Entity Framework Core 5.0 Preview 1

Arthur

March 16th, 2020

Today we are excited to announce the first preview release of EF Core 5.0.

Prerequisites

The previews of EF Core 5.0 require .NET Standard 2.1. This means:

EF Core 5.0 runs on .NET Core 3.1; it does not require .NET 5. This may change in future previews depending on how the plan for .NET 5 evolves.

EF Core 5.0 runs on other platforms that support .NET Standard 2.1.

EF Core 5.0 will not run on .NET Standard 2.0 platforms, including .NET Framework.

How to get EF Core 5.0 previews

EF Core is distributed exclusively as a set of NuGet packages. For example, to add the SQL Server provider to your project, you can use the following command using the dotnet tool:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 5.0.0-preview.2.20120.8

The EF Core packages published today are:

Microsoft.EntityFrameworkCore – The main EF Core package

Microsoft.EntityFrameworkCore.SqlServer – Database provider for Microsoft SQL Server and SQL Azure

Microsoft.EntityFrameworkCore.Sqlite – Database provider for SQLite

Microsoft.EntityFrameworkCore.Cosmos – Database provider for Azure Cosmos DB

Microsoft.EntityFrameworkCore.InMemory – The in-memory database provider

Microsoft.EntityFrameworkCore.Tools – EF Core PowerShell commands for the Visual Studio Package Manager Console

Microsoft.EntityFrameworkCore.Design – Shared design-time components for EF Core tools

Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite – SQL Server support for spatial types

Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite – SQLite support for spatial types

Microsoft.EntityFrameworkCore.Proxies – Lazy-loading and change-tracking proxies

Microsoft.EntityFrameworkCore.Abstractions – Decoupled EF Core abstractions

Microsoft.EntityFrameworkCore.Relational – Shared EF Core components for relational database providers

Microsoft.EntityFrameworkCore.Analyzers – C# analyzers for EF Core

Microsoft.EntityFrameworkCore.Sqlite.Core – Database provider for SQLite without a packaged native binary

We have also published the 5.0 preview 1 release of the Microsoft.Data.Sqlite.Core ADO.NET provider.

Installing dotnet ef

As with EF Core 3.0 and 3.1, the dotnet ef command-line tool is no longer included in the .NET Core SDK. Before you can execute EF Core migration or scaffolding commands, you’ll have to install this package as either a global or local tool.

To install the preview tool globally, first uninstall any existing version with:

dotnet tool uninstall --global dotnet-ef

Then install with:

dotnet tool install --global dotnet-ef --version 5.0.0-preview.2.20120.8

It’s possible to use this new version of dotnet ef with projects that use older versions of the EF Core runtime.

Package version numbers

There was a mistake in the .NET 5 build process that resulted in the EF preview 1 packages being erroneously branded as “5.0.0-preview.2.20120.8”.

This should not have any functional impact. It should also not impact the real Preview 2, which is still planned for later in the year.

What’s new in EF Core 5 Preview 1

We maintain documentation covering new features introduced into each preview.

Some of the highlights from preview 1 are called out below.

Simple Logging

This feature is functionally similar to Database.Log in EF6. That is, it provides a simple way to get logs from EF Core without the need to configure any kind of external logging framework.

EF Core replaces Database.Log with a LogTo method called on DbContextOptionsBuilder in either AddDbContext or OnConfiguring. For example:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.LogTo(Console.WriteLine);

Overloads exist to:

Set the minimum log level Example: .LogTo(Console.WriteLine, LogLevel.Information)

Filter for only specific events: Example: .LogTo(Console.WriteLine, new[] {CoreEventId.ContextInitialized, RelationalEventId.CommandExecuted})

Filter for all events in specific categories: Example: .LogTo(Console.WriteLine, new[] {DbLoggerCategory.Database.Name}, LogLevel.Information)

Use a custom filter over event and level: Example: .LogTo(Console.WriteLine, (id, level) => id == RelationalEventId.CommandExecuting)



Output format can be minimally configured (API is in flux) but the default output looks something like:

warn: 12/5/2019 09:57:47.574 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure) Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data, this mode should only be enabled during development. dbug: 12/5/2019 09:57:47.581 CoreEventId.ShadowPropertyCreated[10600] (Microsoft.EntityFrameworkCore.Model.Validation) The property 'BlogId' on entity type 'Post' was created in shadow state because there are no eligible CLR members with a matching name. info: 12/5/2019 09:57:47.618 CoreEventId.ContextInitialized[10403] (Microsoft.EntityFrameworkCore.Infrastructure) Entity Framework Core 5.0.0-dev initialized 'BloggingContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: SensitiveDataLoggingEnabled dbug: 12/5/2019 09:57:47.644 CoreEventId.ValueGenerated[10808] (Microsoft.EntityFrameworkCore.ChangeTracking) 'BloggingContext' generated temporary value '-2147482647' for the 'Id' property of new 'Blog' entity. ...

Simple way to get generated SQL

EF Core 5.0 introduces the ToQueryString extension method which will return the SQL that EF Core will generate when executing a LINQ query. For example, the code:

var query = context.Set<customer>().Where(c => c.City == city); Console.WriteLine(query.ToQueryString())

results in this output when using the SQL Server database provider:

DECLARE p0 nvarchar(4000) = N'London'; SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] WHERE [c].[City] = @__city_0

Notice that declarations for parameters of the correct type are also included in the output. This allows copy/pasting to SQL Server Management Studio, or similar tools, such that the query can be executed for debugging/analysis.

Use a C# attribute to indicate that an entity has no key

An entity type can now be configured as having no key using the new KeylessAttribute . For example:

[Keyless] public class Address { public string Street { get; set; } public string City { get; set; } public int Zip { get; set; } }

Connection or connection string can be changed on initialized DbContext

It is now easier to create a DbContext instance without any connection or connection string. Also, the connection or connection string can now be mutated on the context instance. This allows the same context instance to dynamically connect to different databases.

Change-tracking proxies

EF Core can now generate runtime proxies that automatically implement INotifyPropertyChanging and INotifyPropertyChanged. These then report value changes on entity properties directly to EF Core, avoiding the need to scan for changes. However, proxies come with their own set of limitations, so they are not for everyone.

Enhanced debug views

Debug views are an easy way to look at the internals of EF Core when debugging issues. A debug view for the Model was implemented some time ago. For EF Core 5.0, we have made the model view easier to read and added a new debug view for tracked entities in the state manager.

Model debug view

Drill down into the Model property of the DbContext in your debugger of choice and expand the DebugView property.

The LongView is the model view we have had for some time. The ShortView is new and doesn’t include model annotations, which make it much easier to read. For example, here is one of our test models:

Model: EntityType: Chassis Properties: TeamId (int) Required PK FK AfterSave:Throw Name (string) Version (no field, byte[]) Shadow Concurrency BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate Navigations: Team (_team, Team) ToPrincipal Team Inverse: Chassis PropertyAccessMode.Field Keys: TeamId PK Foreign keys: Chassis {'TeamId'} -> Team {'Id'} Unique ToDependent: Chassis ToPrincipal: Team EntityType: Driver Properties: Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd CarNumber (Nullable<int>) Championships (int) Required Discriminator (no field, string) Shadow Required FastestLaps (int) Required Name (string) Podiums (int) Required Poles (int) Required Races (int) Required TeamId (int) Required FK Index Version (no field, byte[]) Shadow Concurrency BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate Wins (int) Required Navigations: Team (_team, Team) ToPrincipal Team Inverse: Drivers PropertyAccessMode.Field Keys: Id PK Foreign keys: Driver {'TeamId'} -> Team {'Id'} ToDependent: Drivers ToPrincipal: Team Indexes: TeamId EntityType: Engine Properties: Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd EngineSupplierId (int) Required FK Index Concurrency Name (string) Concurrency Navigations: EngineSupplier (_engineSupplier, EngineSupplier) ToPrincipal EngineSupplier Inverse: Engines PropertyAccessMode.Field Gearboxes (_gearboxes, ICollection<gearbox>) Collection ToDependent Gearbox PropertyAccessMode.Field StorageLocation (Location) ToDependent Location PropertyAccessMode.Field Teams (_teams, ICollection<team>) Collection ToDependent Team Inverse: Engine PropertyAccessMode.Field Keys: Id PK Foreign keys: Engine {'EngineSupplierId'} -> EngineSupplier {'Id'} ToDependent: Engines ToPrincipal: EngineSupplier Indexes: EngineSupplierId EntityType: EngineSupplier Properties: Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd Name (string) Navigations: Engines (_engines, ICollection<engine>) Collection ToDependent Engine Inverse: EngineSupplier PropertyAccessMode.Field Keys: Id PK EntityType: Gearbox Properties: Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd EngineId (no field, Nullable<int>) Shadow FK Index Name (string) Keys: Id PK Foreign keys: Gearbox {'EngineId'} -> Engine {'Id'} ToDependent: Gearboxes Indexes: EngineId EntityType: Location Properties: EngineId (no field, int) Shadow Required PK FK AfterSave:Throw ValueGenerated.OnAdd Latitude (double) Required Concurrency Longitude (double) Required Concurrency Keys: EngineId PK Foreign keys: Location {'EngineId'} -> Engine {'Id'} Unique Ownership ToDependent: StorageLocation EntityType: Sponsor Properties: Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd ClientToken (no field, Nullable</int><int>) Shadow Concurrency Discriminator (no field, string) Shadow Required Name (string) Version (no field, byte[]) Shadow Concurrency BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate Keys: Id PK EntityType: SponsorDetails Properties: TitleSponsorId (no field, int) Shadow Required PK FK AfterSave:Throw ValueGenerated.OnAdd ClientToken (no field, Nullable</int><int>) Shadow Concurrency Days (int) Required Space (decimal) Required Version (no field, byte[]) Shadow Concurrency BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate Keys: TitleSponsorId PK Foreign keys: SponsorDetails {'TitleSponsorId'} -> TitleSponsor {'Id'} Unique Ownership ToDependent: Details EntityType: Team Properties: Id (int) Required PK AfterSave:Throw Constructor (string) ConstructorsChampionships (int) Required DriversChampionships (int) Required EngineId (no field, Nullable</int><int>) Shadow FK Index FastestLaps (int) Required GearboxId (Nullable</int><int>) FK Index Name (string) Poles (int) Required Principal (string) Races (int) Required Tire (string) Version (no field, byte[]) Shadow Concurrency BeforeSave:Ignore AfterSave:Ignore ValueGenerated.OnAddOrUpdate Victories (int) Required Navigations: Chassis (_chassis, Chassis) ToDependent Chassis Inverse: Team PropertyAccessMode.Field Drivers (_drivers, ICollection<driver>) Collection ToDependent Driver Inverse: Team PropertyAccessMode.Field Engine (_engine, Engine) ToPrincipal Engine Inverse: Teams PropertyAccessMode.Field Gearbox (_gearbox, Gearbox) ToPrincipal Gearbox PropertyAccessMode.Field Keys: Id PK Foreign keys: Team {'EngineId'} -> Engine {'Id'} ToDependent: Teams ToPrincipal: Engine Team {'GearboxId'} -> Gearbox {'Id'} Unique ToPrincipal: Gearbox Indexes: EngineId GearboxId Unique EntityType: TestDriver Base: Driver EntityType: TitleSponsor Base: Sponsor Navigations: Details (_details, SponsorDetails) ToDependent SponsorDetails PropertyAccessMode.Field

State manager debug view

The state manager is a little mode hidden than the model. To find it, drill down into the ChangeTracker property of the DbContext in your debugger of choice and then look in the StateManager property and expand the DebugView .

The short view of the state manager shows:

Each entity being tracked

Its primary key value(s)

The entity state – one of Added, Unchanged, Modified, or Deleted.

The values of an foreign key properties

For example:

Engine (Shared) {Id: 1} Unchanged FK {EngineSupplierId: 1} Location (Shared) {EngineId: 1} Unchanged FK {EngineId: 1} Team (Shared) {Id: 4} Modified FK {EngineId: 1} FK {GearboxId: <null>}

The long view shows everything in the short view together with:

The current value of each property

Whether or not the property is marked as modified

The original value of the property, if different from the current value

The entity referenced by a reference navigation using the referenced entity’s primary key value

The list if entities referenced by a collection navigation, again using primary key values

For example:

Engine (Shared) {Id: 1} Unchanged Id: 1 PK EngineSupplierId: 1 FK Name: 'FO 108X' EngineSupplier: <null> Gearboxes: </null><null> StorageLocation: {EngineId: 1} Teams: [{Id: 4}] Location (Shared) {EngineId: 1} Unchanged EngineId: 1 PK FK Latitude: 47.64491 Longitude: -122.128101 Team (Shared) {Id: 4} Modified Id: 4 PK Constructor: 'Ferrari' ConstructorsChampionships: 16 DriversChampionships: 15 EngineId: 1 FK Modified Originally 3 FastestLaps: 221 GearboxId: </null><null> FK Name: 'Scuderia Ferrari Marlboro' Poles: 203 Principal: 'Stefano Domenicali' Races: 805 Tire: 'Bridgestone' Version: '0x000000000001405A' Victories: 212 Chassis: </null><null> Drivers: [] Engine: {Id: 1} Gearbox: </null><null>

Improved handling of database null semantics

Relational databases typically treat NULL as an unknown value and therefore not equal to any other NULL. C#, on the other hand, treats null as a defined value which compares equal to any other null. EF Core by default translates queries so that they use C# null semantics. EF Core 5.0 greatly improves the efficiency of these translations.

Indexer properties

EF Core 5.0 supports mapping of C# indexer properties. This allows entities to act as property bags where columns are mapped to named properties in the bag.

Generation of check constraints for enum mappings

EF Core 5.0 Migrations can now generate CHECK constraints for enum property mappings. For example:

EnumColumn VARCHAR(10) NOT NULL CHECK (MyEnumColumn IN('Useful', 'Useless', 'Unknown'))

IsRelational

A new IsRelational method has been added in addition to the existing IsSqlServer , IsSqlite , and IsInMemory . This can be used to test if the DbContext is using any relational database provider. For example:

protected override void OnModelCreating(ModelBuilder modelBuilder) { if (Database.IsRelational()) { // Do relational-specific model configuration. } }

The Azure Cosmos DB database provider now supports optimistic concurrency using ETags. Use the model builder in OnModelCreating to configure an ETag:

builder.Entity<customer>().Property(c => c.ETag).IsEtagConcurrency();

SaveChanges will then throw an DbUpdateConcurrencyException on a concurrency conflict, which can be handled to implement retries, etc.

Queries containing new DateTime construction are now translated.

In addition, the following SQL Server functions are now mapped: * DateDiffWeek * DateFromParts

For example:

var count = context.Orders.Count(c => date > EF.Functions.DateFromParts(DateTime.Now.Year, 12, 25));

Query translations for more byte array constructs

Queries using Contains, Length, SequenceEqual, etc. on byte[] properties are now translated to SQL. For example:

var blogs = context.Blogs.Where(e => e.Picture.Contains((byte)127)).ToList();

Query translation for Reverse

Queries using Reverse are now translated. For example:

context.Employees.OrderBy(e => e.EmployeeID).Reverse()

Query translation for bitwise operators

Queries using bitwise operators are now translated in more cases. For example:

context.Orders.Where(o => ~o.OrderID == negatedId)

Query translation for strings on Cosmos

Queries that use the string methods Contains, StartsWith, and EndsWith are now translated when using the Azure Cosmos DB provider.

Daily builds

EF Core previews are aligned with .NET 5 previews. These previews tend to lag behind the latest work on EF Core. Consider using the daily builds instead to get the most up-to-date EF Core features and bug fixes.

As with the previews, the daily builds do not require .NET 5; they can be used with GA/RTM release of .NET Core 3.1.

Documentation and feedback

The starting point for all EF Core documentation is docs.microsoft.com/ef/core/.

Please file issues found and any other feedback on the dotnet/efcore GitHub repo.

Thank you from the team!

A big thank you from the EF team to everyone who has used EF over the years!

Thank you to our contributors!

A big thank you to the following community members who have already contributed code or documentation to the EF Core 5 release!