Announcing Entity Framework Core (EFCore) 5.0 RC1

Jeremy Likness

Jeremy

Today, the Entity Framework Core team announces the first release candidate (RC1) of EF Core 5.0. This is a feature complete release candidate of EF Core 5.0 and ships with a "go live" license. You are supported using it in production. This is a great opportunity to start using EF Core 5.0 early while there is still time to fix remaining issues. We're looking for reports of any remaining critical bugs that should be fixed before the final release.

This release includes new features like many-to-many, property bags, event counters, required 1:1 dependents and the ability to intercept SaveChanges and listen to save events. It also includes improvements to model-building, migrations, and more. A more in-depth look at what's new follows later in this blog post.

Prerequisites

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

How to get EF Core 5.0 Release Candidate 1

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-rc.1.20451.13

This following table links to the RC1 versions of the EF Core packages and describes what they are used for.

PackagePurpose
Microsoft.EntityFrameworkCoreThe main EF Core package that is independent of specific database providers
Microsoft.EntityFrameworkCore.SqlServerDatabase provider for Microsoft SQL Server and SQL Azure
Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuiteSQL Server support for spatial types
Microsoft.EntityFrameworkCore.SqliteDatabase provider for SQLite that includes the native binary for the database engine
Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuiteSQLite support for spatial types
Microsoft.EntityFrameworkCore.CosmosDatabase provider for Azure Cosmos DB
Microsoft.EntityFrameworkCore.InMemoryThe in-memory database provider
Microsoft.EntityFrameworkCore.ToolsEF Core PowerShell commands for the Visual Studio Package Manager Console; use this to integrate tools like scaffolding and migrations with Visual Studio
Microsoft.EntityFrameworkCore.DesignShared design-time components for EF Core tools
Microsoft.EntityFrameworkCore.ProxiesLazy-loading and change-tracking proxies
Microsoft.EntityFrameworkCore.AbstractionsDecoupled EF Core abstractions; use this for features like extended data annotations defined by EF Core
Microsoft.EntityFrameworkCore.RelationalShared EF Core components for relational database providers
Microsoft.EntityFrameworkCore.AnalyzersC# analyzers for EF Core

Installing the EF Core Command Line Interface (CLI)

As with EF Core 3.0 and 3.1, the EF Core CLI 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.

dotnet-ef

To install the RC1 tool globally the first time, use:

dotnet tool install --global dotnet-ef --version 5.0.0-rc.1.20451.13

If you already have the tool installed, update it with:

dotnet tool update --global dotnet-ef --version 5.0.0-rc.1.20451.13

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

What's New in EF Core 5 RC1

We maintain documentation covering new features introduced into each release.

Some of the highlights from RC1 are called out below. This release candidate also includes several bug fixes.

Many-to-many

EF Core 5.0 supports many-to-many relationships without explicitly mapping the join table.

For example, consider these entity types:

public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public int Id { get; set; }
    public string Text { get; set; }
    public ICollection<Post> Posts { get; set; }
}

Notice that Post contains a collection of Tags, and Tag contains a collection of Posts. EF Core 5.0 recognizes this as a many-to-many relationship by convention. This means no code is required in OnModelCreating:

public class BlogContext : DbContext
{
    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }
}

When Migrations (or EnsureCreated) are used to create the database, EF Core will automatically create the join table. For example, on SQL Server for this model, EF Core generates:

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id])
);

CREATE TABLE [Tag] (
    [Id] int NOT NULL IDENTITY,
    [Text] nvarchar(max) NULL,
    CONSTRAINT [PK_Tag] PRIMARY KEY ([Id])
);

CREATE TABLE [PostTag] (
    [PostsId] int NOT NULL,
    [TagsId] int NOT NULL,
    CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]),
    CONSTRAINT [FK_PostTag_Posts_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_PostTag_Tag_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tag] ([Id]) ON DELETE CASCADE
);

CREATE INDEX [IX_PostTag_TagsId] ON [PostTag] ([TagsId]);

Creating and associating Blog and Post entities results in join table updates happening automatically. For example:

var beginnerTag = new Tag {Text = "Beginner"};
var advancedTag = new Tag {Text = "Advanced"};
var efCoreTag = new Tag {Text = "EF Core"};

context.AddRange(
    new Post {Name = "EF Core 101", Tags = new List<Tag> {beginnerTag, efCoreTag}},
    new Post {Name = "Writing an EF database provider", Tags = new List<Tag> {advancedTag, efCoreTag}},
    new Post {Name = "Savepoints in EF Core", Tags = new List<Tag> {beginnerTag, efCoreTag}});

context.SaveChanges();

After inserting the Posts and Tags, EF will then automatically create rows in the join table. For example, on SQL Server:

SET NOCOUNT ON;
INSERT INTO [PostTag] ([PostsId], [TagsId])
VALUES (@p6, @p7),
(@p8, @p9),
(@p10, @p11),
(@p12, @p13),
(@p14, @p15),
(@p16, @p17);

For queries, Include and other query operations work just like for any other relationship. For example:

foreach (var post in context.Posts.Include(e => e.Tags))
{
    Console.Write($"Post \"{post.Name}\" has tags");

    foreach (var tag in post.Tags)
    {
        Console.Write($" '{tag.Text}'");
    }
}

The SQL generated uses the join table automatically to bring back all related Tags:

SELECT [p].[Id], [p].[Name], [t0].[PostsId], [t0].[TagsId], [t0].[Id], [t0].[Text]
FROM [Posts] AS [p]
LEFT JOIN (
    SELECT [p0].[PostsId], [p0].[TagsId], [t].[Id], [t].[Text]
    FROM [PostTag] AS [p0]
    INNER JOIN [Tag] AS [t] ON [p0].[TagsId] = [t].[Id]
) AS [t0] ON [p].[Id] = [t0].[PostsId]
ORDER BY [p].[Id], [t0].[PostsId], [t0].[TagsId], [t0].[Id]

Unlike EF6, EF Core allows full customization of the join table. For example, the code below configures a many-to-many relationship that also has navigations to the join entity, and in which the join entity contains a payload property:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Community>()
        .HasMany(e => e.Members)
        .WithMany(e => e.Memberships)
        .UsingEntity<PersonCommunity>(
            b => b.HasOne(e => e.Member).WithMany().HasForeignKey(e => e.MembersId),
            b => b.HasOne(e => e.Membership).WithMany().HasForeignKey(e => e.MembershipsId))
        .Property(e => e.MemberSince).HasDefaultValueSql("CURRENT_TIMESTAMP");
}

Map entity types to queries

Entity types are commonly mapped to tables or views such that EF Core will pull back the contents of the table or view when querying for that type. EF Core 5.0 allows an entity type to mapped to a "defining query". (This was partially supported in previous versions, but is much improved and has different syntax in EF Core 5.0.)

For example, consider two tables; one with modern posts; the other with legacy posts. The modern posts table has some additional columns, but for the purpose of our application we want both modern and legacy posts tp be combined and mapped to an entity type with all necessary properties:

public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Category { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

In EF Core 5.0, ToSqlQuery can be used to map this entity type to a query that pulls and combines rows from both tables:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>().ToSqlQuery(
        @"SELECT Id, Name, Category, BlogId FROM posts
          UNION ALL
          SELECT Id, Name, ""Legacy"", BlogId from legacy_posts");
}

Notice that the legacy_posts table does not have a Category column, so we instead synthesize a default value for all legacy posts.

This entity type can then be used in the normal way for LINQ queries. For example. the LINQ query:

var posts = context.Posts.Where(e => e.Blog.Name.Contains("Unicorn")).ToList();

Generates the following SQL on SQLite:

SELECT "p"."Id", "p"."BlogId", "p"."Category", "p"."Name"
FROM (
    SELECT Id, Name, Category, BlogId FROM posts
    UNION ALL
    SELECT Id, Name, "Legacy", BlogId from legacy_posts
) AS "p"
INNER JOIN "Blogs" AS "b" ON "p"."BlogId" = "b"."Id"
WHERE ('Unicorn' = '') OR (instr("b"."Name", 'Unicorn') > 0)

Notice that the query configured for the entity type is used as a starting for composing the full LINQ query.

Event counters

.NET event counters are a way to efficiently expose performance metrics from an application. EF Core 5.0 includes event counters under the Microsoft.EntityFrameworkCore category. For example:

dotnet counters monitor Microsoft.EntityFrameworkCore -p 49496

This tells dotnet counters to start collecting EF Core events for process 49496. This generates output like this in the console:

[Microsoft.EntityFrameworkCore]
    Active DbContexts                                               1
    Execution Strategy Operation Failures (Count / 1 sec)           0
    Execution Strategy Operation Failures (Total)                   0
    Optimistic Concurrency Failures (Count / 1 sec)                 0
    Optimistic Concurrency Failures (Total)                         0
    Queries (Count / 1 sec)                                     1,755
    Queries (Total)                                            98,402
    Query Cache Hit Rate (%)                                      100
    SaveChanges (Count / 1 sec)                                     0
    SaveChanges (Total)                                             1

Property bags

EF Core 5.0 allows the same CLR type to be mapped to multiple different entity types. Such types are known as shared-type entity types. This feature combined with indexer properties (included in preview 1) allows property bags to be used as entity type.

For example, the DbContext below configures the BCL type Dictionary<string, object> as a shared-type entity type for both products and categories.

public class ProductsContext : DbContext
{
    public DbSet<Dictionary<string, object>> Products => Set<Dictionary<string, object>>("Product");
    public DbSet<Dictionary<string, object>> Categories => Set<Dictionary<string, object>>("Category");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Category", b =>
        {
            b.IndexerProperty<string>("Description");
            b.IndexerProperty<int>("Id");
            b.IndexerProperty<string>("Name").IsRequired();
        });

        modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Product", b =>
        {
            b.IndexerProperty<int>("Id");
            b.IndexerProperty<string>("Name").IsRequired();
            b.IndexerProperty<string>("Description");
            b.IndexerProperty<decimal>("Price");
            b.IndexerProperty<int?>("CategoryId");

            b.HasOne("Category", null).WithMany();
        });
    }
}

Dictionary objects ("property bags") can now be added to the context as entity instances and saved. For example:

var beverages = new Dictionary<string, object>
{
    ["Name"] = "Beverages",
    ["Description"] = "Stuff to sip on"
};

context.Categories.Add(beverages);

context.SaveChanges();

These entities can then be queried and updated in the normal way:

var foods = context.Categories.Single(e => e["Name"] == "Foods");
var marmite = context.Products.Single(e => e["Name"] == "Marmite");

marmite["CategoryId"] = foods["Id"];
marmite["Description"] = "Yummy when spread _thinly_ on buttered Toast!";

context.SaveChanges();

SaveChanges interception and events

EF Core 5.0 introduces both .NET events and an EF Core interceptor triggered when SaveChanges is called.

The events are simple to use; for example:

context.SavingChanges += (sender, args) =>
{
    Console.WriteLine($"Saving changes for {((DbContext)sender).Database.GetConnectionString()}");
};

context.SavedChanges += (sender, args) =>
{
    Console.WriteLine($"Saved {args.EntitiesSavedCount} changes for {((DbContext)sender).Database.GetConnectionString()}");
};

Notice that:

  • The event sender is the DbContext instance
  • The args for the SavedChanges event contains the number of entities saved to the database

The interceptor is defined by ISaveChangesInterceptor, but it is often convienient to inherit from SaveChangesInterceptor to avoid implementing every method. For example:

public class MySaveChangesInterceptor : SaveChangesInterceptor
{
    public override InterceptionResult<int> SavingChanges(
        DbContextEventData eventData,
        InterceptionResult<int> result)
    {
        Console.WriteLine($"Saving changes for {eventData.Context.Database.GetConnectionString()}");

        return result;
    }

    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = new CancellationToken())
    {
        Console.WriteLine($"Saving changes asynchronously for {eventData.Context.Database.GetConnectionString()}");

        return new ValueTask<InterceptionResult<int>>(result);
    }
}

Notice that:

  • The interceptor has both sync and async methods. This can be useful if you need to perform async I/O, such as writing to an audit server.
  • The interceptor allows SaveChanges to be skipped using the InterceptionResult mechanism common to all interceptors.

The downside of interceptors is that they must be registered on the DbContext when it is being constructed. For example:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .AddInterceptors(new MySaveChangesInterceptor())
            .UseSqlite("Data Source = test.db");

In contrast, the events can be registered on the DbContext instance at any time.

Exclude tables from migrations

It is sometimes useful to have a single entity type mapped in multiple DbContexts. This is especially true when using bounded contexts, for which it is common to have a different DbContext type for each bounded context.

For example, a User type may be needed by both an authorization context and a reporting context. If a change is made to the User type, then migrations for both DbContexts will attempt to update the database. To prevent this, the model for one of the contexts can be configured to exclude the table from its migrations.

In the code below, the AuthorizationContext will generate migrations for changes to the Users table, but the ReportingContext will not, preventing the migrations from clashing.

public class AuthorizationContext : DbContext
{
    public DbSet<User> Users { get; set; }
}

public class ReportingContext : DbContext
{
    public DbSet<User> Users { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().ToTable("Users", t => t.ExcludeFromMigrations());
    }
}

Required 1:1 dependents

In EF Core 3.1, the dependent end of a one-to-one relationship was always considered optional. This was most apparent when using owned entities. For example, consider the following model and configuration:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }

    public Address HomeAddress { get; set; }
    public Address WorkAddress { get; set; }
}

public class Address
{
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
    public string Region { get; set; }
    public string Country { get; set; }
    public string Postcode { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>(b =>
    {
        b.OwnsOne(e => e.HomeAddress,
            b =>
            {
                b.Property(e => e.Line1).IsRequired();
                b.Property(e => e.City).IsRequired();
                b.Property(e => e.Region).IsRequired();
                b.Property(e => e.Postcode).IsRequired();
            });

        b.OwnsOne(e => e.WorkAddress);
    });
}

This results in Migrations creating the following table for SQLite:

CREATE TABLE "People" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_People" PRIMARY KEY AUTOINCREMENT,
    "Name" TEXT NULL,
    "HomeAddress_Line1" TEXT NULL,
    "HomeAddress_Line2" TEXT NULL,
    "HomeAddress_City" TEXT NULL,
    "HomeAddress_Region" TEXT NULL,
    "HomeAddress_Country" TEXT NULL,
    "HomeAddress_Postcode" TEXT NULL,
    "WorkAddress_Line1" TEXT NULL,
    "WorkAddress_Line2" TEXT NULL,
    "WorkAddress_City" TEXT NULL,
    "WorkAddress_Region" TEXT NULL,
    "WorkAddress_Country" TEXT NULL,
    "WorkAddress_Postcode" TEXT NULL
);

Notice that all the columns are nullable, even though some of the HomeAddress properties have been configured as required. Also, when querying for a Person, if all the columns for either the home or work address are null, then EF Core will leave the HomeAddress and/or WorkAddress properties as null, rather than setting an empty instance of Address.

In EF Core 5.0, the HomeAddress navigation can now be configured as as a required dependent. For example:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>(b =>
    {
        b.OwnsOne(e => e.HomeAddress,
            b =>
            {
                b.Property(e => e.Line1).IsRequired();
                b.Property(e => e.City).IsRequired();
                b.Property(e => e.Region).IsRequired();
                b.Property(e => e.Postcode).IsRequired();
            });
        b.Navigation(e => e.HomeAddress).IsRequired();

        b.OwnsOne(e => e.WorkAddress);
    });
}

The table created by Migrations will now included non-nullable columns for the required properties of the required dependent:

CREATE TABLE "People" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_People" PRIMARY KEY AUTOINCREMENT,
    "Name" TEXT NULL,
    "HomeAddress_Line1" TEXT NOT NULL,
    "HomeAddress_Line2" TEXT NULL,
    "HomeAddress_City" TEXT NOT NULL,
    "HomeAddress_Region" TEXT NOT NULL,
    "HomeAddress_Country" TEXT NULL,
    "HomeAddress_Postcode" TEXT NOT NULL,
    "WorkAddress_Line1" TEXT NULL,
    "WorkAddress_Line2" TEXT NULL,
    "WorkAddress_City" TEXT NULL,
    "WorkAddress_Region" TEXT NULL,
    "WorkAddress_Country" TEXT NULL,
    "WorkAddress_Postcode" TEXT NULL
);

In addition, EF Core will now throw an exception if an attempt is made to save an owner which has a null required dependent. In this example, EF Core will throw when attempting to save a Person with a null HomeAddress.

Finally, EF Core will still create an instance of a required dependent even when all the columns for the required dependent have null values.

Options for migration generation

EF Core 5.0 introduces greater control over generation of migrations for different purposes. This includes the ability to:

  • Know if the migration is being generated for a script or for immediate execution
  • Know if an idempotent script is being generated
  • Know if the script should exclude transaction statements (See Migrations scripts with transactions below.)

This behavior is specified by an the MigrationsSqlGenerationOptions enum, which can now be passed to IMigrator.GenerateScript.

Also included in this work is better generation of idempotent scripts with calls to EXEC on SQL Server when needed. This work also enables similar improvements to the scripts generated by other database providers, including PostgreSQL.

Migrations scripts with transactions

SQL scripts generated from migrations now contain statements to begin and commit transactions as appropriate for the migration. For example, the migration script below was generated from two migrations. Notice that each migration is now applied inside a transaction.

BEGIN TRANSACTION;
GO

CREATE TABLE [Groups] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NULL,
    CONSTRAINT [PK_Groups] PRIMARY KEY ([Id])
);
GO

CREATE TABLE [Members] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NULL,
    [GroupId] int NULL,
    CONSTRAINT [PK_Members] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Members_Groups_GroupId] FOREIGN KEY ([GroupId]) REFERENCES [Groups] ([Id]) ON DELETE NO ACTION
);
GO

CREATE INDEX [IX_Members_GroupId] ON [Members] ([GroupId]);
GO

INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20200910194835_One', N'6.0.0-alpha.1.20460.2');
GO

COMMIT;
GO

BEGIN TRANSACTION;
GO

EXEC sp_rename N'[Groups].[Name]', N'GroupName', N'COLUMN';
GO

INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20200910195234_Two', N'6.0.0-alpha.1.20460.2');
GO

COMMIT;

As mentioned in the previous section, this use of transactions can be disabled if transactions need to be handled differently.

See pending migrations

This feature was contributed from the community by @Psypher9. Many thanks for the contribution!

The dotnet ef migrations list command now shows which migrations have not yet been applied to the database. For example:

ajcvickers@avickers420u:~/AllTogetherNow/Daily$ dotnet ef migrations list
Build started...
Build succeeded.
20200910201647_One
20200910201708_Two
20200910202050_Three (Pending)
ajcvickers@avickers420u:~/AllTogetherNow/Daily$

In addition, there is now a Get-Migration command for the Package Manager Console with the same functionality.

ModelBuilder API for value comparers

EF Core properties for custom mutable types require a value comparer for property changes to be detected correctly. This can now be specified as part of configuring the value conversion for the type. For example:

modelBuilder
    .Entity<EntityType>()
    .Property(e => e.MyProperty)
    .HasConversion(
        v => JsonSerializer.Serialize(v, null),
        v => JsonSerializer.Deserialize<List<int>>(v, null),
        new ValueComparer<List<int>>(
            (c1, c2) => c1.SequenceEqual(c2),
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
            c => c.ToList()));

EntityEntry TryGetValue methods

This feature was contributed from the community by @m4ss1m0g. Many thanks for the contribution!

A TryGetValue method has been added to EntityEntry.CurrentValues and EntityEntry.OriginalValues. This allows the value of a property to be requested without first checking if the property is mapped in the EF model. For example:

if (entry.CurrentValues.TryGetValue(propertyName, out var value))
{
    Console.WriteLine(value);
}

Default max batch size for SQL Server

Starting with EF Core 5.0, the default maximum batch size for SaveChanges on SQL Server is now 42. As is well known, this is also the answer to the Ultimate Question of Life, the Universe, and Everything. However, this is probably a coincidence, since the value was obtained through analysis of batching performance. We do not believe that we have discovered a form of the Ultimate Question, although it does seem somewhat plausible that the Earth was created to understand why SQL Server works the way it does.

Default environment to Development

The EF Core command line tools now automatically configure the ASPNETCORE_ENVIRONMENT and DOTNET_ENVIRONMENT environment variables to "Development". This brings the experience when using the generic host in line with the experience for ASP.NET Core during development. See #19903.

Better migrations column ordering

The columns for unmapped base classes are now ordered after other columns for mapped entity types. Note this only impacts newly created tables. The column order for existing tables remains unchanged. See #11314.

Query improvements

EF Core 5.0 RC1 contains some additional query translation improvements:

  • Translation of is on Cosmos–see #16391
  • User-mapped functions can now be annotated to control null propagation–see #19609
  • Support for translation of GroupBy with conditional aggregates–see #11711
  • Translation of Distinct operator over group element before aggregate–see #17376

Model building for fields

Finally for RC1, EF Core now allows use of the lambda methods in the ModelBuilder for fields as well as properties. For example, if you are averse to properties for some reason and decide to use public fields, then these fields can now be mapped using the lambda builders:

public class Post
{
    public int Id;
    public string Name;
    public string Category;
    public int BlogId;
    public Blog Blog;
}

public class Blog
{
    public int Id;
    public string Name;
    public ICollection<Post> Posts;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(b =>
    {
        b.Property(e => e.Id);
        b.Property(e => e.Name);
    });

    modelBuilder.Entity<Post>(b =>
    {
        b.Property(e => e.Id);
        b.Property(e => e.Name);
        b.Property(e => e.Category);
        b.Property(e => e.BlogId);
        b.HasOne(e => e.Blog).WithMany(e => e.Posts);
    });
}

While this is now possible, we are certainly not recommending that you do this. Also, note that this does not add any additional field mapping capabilities to EF Core, it only allows the lambda methods to be used instead of always requiring the string methods. This is seldom useful since fields are rarely public.

Daily builds

EF Core previews and release candidates are aligned with the .NET 5 release cycle. These releases 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. Daily builds are considered stable.

Contribute to .NET 5

The .NET documentation team is reorganizing .NET content to better match the workloads you build with .NET. This includes a new .NET Data landing page that will link out to data-related topics ranging from EF Core to APIs, Big Data, and Machine learning. The planning and execution will be done completely in the open on GitHub. This is your opportunity to help shape the hierarchy and content to best fit your needs as a .NET developer. We look forward to your contributions!

The EF Core Community Standup

The EF Core team is now live streaming every other Wednesday at 10am Pacific Time, 1pm Eastern Time, or 17:00 UTC. Join the stream to ask questions about the EF Core topic of your choice, including the latest release candidate.

Documentation and Feedback

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

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

The following short links are provided for easy reference and access.

Main documentation: https://aka.ms/efdocs

Issues and feature requests for EF Core: https://aka.ms/efcorefeedback

Entity Framework Roadmap: https://aka.ms/efroadmap

What's new in EF Core 5.x? https://aka.ms/efcore5

Thank you from the team

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

ajcvickers
Arthur Vickers
AndriySvyryd
Andriy Svyryd

Brice Lambson
JeremyLikness
Jeremy Likness
maumar
Maurycy Markowski
roji
Shay Rojansky
smitpatel
Smit Patel
 

Thank you to our contributors!

A huge "thanks" to the following community members who have already contributed code or documentation to the EF Core 5 release! (List is in chronological order of first contribution to EF Core 5).

divega
Diego Vega
lajones
lajones
aevitas
aevitas
alaatm
Alaa Masoud
aleksandar-manukov
Aleksandar Manukov
amrbadawy
Amr Badawy
AnthonyMonterrosa
Anthony Monterrosa
bbrandt
Ben Brandt
benmccallum
Ben McCallum
ccjx
Clarence Cai
CGijbels
Christophe Gijbels
cincuranet
Jiri Cincura
Costo
Vincent Costel
dshuvaev
Dmitry Shuvaev
EricStG
Eric St-Georges
ErikEJ
Erik Ejlskov Jensen
gravbox
Christopher Davis
ivaylokenov
Ivaylo Kenov
jfoshee
Jacob Foshee
jmzagorski
Jeremy Zagorski
jviau
Jacob Viau
knom
Max K.
lohoris-crane
lohoris-crane
loic-sharma
Loïc Sharma
lokalmatador
lokalmatador
mariusGundersen
Marius Gundersen
Marusyk
Roman Marusyk
matthiaslischka
Matthias Lischka
MaxG117
MaxG117
MHDuke
MHDuke
mikes-gh
Mike Surcouf
Muppets
Neil Bostrom
nmichels
Nícolas Michels
OOberoi
Obi Oberoi
orionstudt
Josh Studt
ozantopal
Ozan Topal
pmiddleton
Paul Middleton
prog-rajkamal
Raj
ptjhuang
Peter Huang
ralmsdeveloper
Rafael Almeida Santos
redoz
Patrik Husfloen
rmarskell
Richard Marskell
sguitardude
sguitardude
SimpleSamples
Sam Hobbs
svengeance
Sven
VladDragnea
Vlad
vslee
vslee
WeihanLi
liweihan
Youssef1313
Youssef Victor
1iveowl
1iveowl
thomaslevesque
Thomas Levesque
akovac35
Aleksander Kovač
leotsarev
Leonid Tsarev
kostat
Konstantin Triger
sungam3r
Ivan Maximov
dzmitry-lahoda
Dzmitry Lahoda
Logerfo
Bruno Logerfo
witheej
Josh Withee
FransBouma
Frans Bouma
IGx89
Matthew Lieder
paulomorgado
Paulo Morgado
mderriey
Mickaël Derriey
LaurenceJKing
Laurence King
oskarj
Oskar Josefsson
bdebaere
bdebaere
BhargaviAnnadevara-MSFT
Bhargavi Annadevara
AlexanderTaeschner
Alexander Täschner
Jesse-Hufstetler
Jesse Hufstetler
ivarlovlie
Ivar Løvlie
cucoreanu
cucoreanu
serpent5
Kirk Larkin
sdanyliv
Svyatoslav Danyliv
twenzel
Toni Wenzel
manvydasu
manvydasu
brandongregoryscott
Brandon Scott
uncheckederror
Thomas Ryan
rocke97
Aaron Gunther
jonlouie
Jon Louie
mohsinnasir
Mohsin Nasir
seekingtheoptimal
Bálint Szabó
MartinBP
Martin Boye Petersen
Ropouser
Duje Đaković
codemillmatt
Matt Soucoup
shahabganji
Saeed Ganji
AshkanAbd
Ashkan Abd
TheFanatr
Alex Fanat
0xced
Cédric Luthi
nbuuck
Nathan Buuck
  

11 comments

Leave a comment

  • Avatar
    Markus Bauer

    Hello Jeremy,
    I’m a little bit confused concerning the Many-to-many example.
    There are 2 entity types: post and tag.
    But the DB Context is showing
    public DbSet Posts { get; set; }
    public DbSet Blogs { get; set; }
    Is this right? I’m not an EF specialist, but I would expect the following:
    public DbSet Posts { get; set; }
    public DbSet Tags { get; set; }
    Best Markus

  • Avatar
    Alexandr B

    Hello Jeremy

    How to scaffold many-to-many relations in the existing database without join entity?

    I have the Article, ArticleGroup, and ArticleToArticleGroup tables. Scaffolding generates entities for all of them. And Article entity has the ‘ArticleArticleGroups’ property but doesn’t have ‘Groups’ property.

    CREATE TABLE [dbo].[ArticleToArticleGroup](
        [ArticleGroupID] [int] NOT NULL,
        [ArticleID] [int] NOT NULL,
     CONSTRAINT [PK_ArticleToArticleGroup] PRIMARY KEY CLUSTERED 
    (
        [ArticleGroupID] ASC,
        [ArticleID] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    ALTER TABLE [dbo].[ArticleToArticleGroup]  WITH CHECK ADD  CONSTRAINT [FK_ArticleToArticleGroup_Article] FOREIGN KEY([ArticleID])
    REFERENCES [dbo].[Article] ([ID])
    ALTER TABLE [dbo].[ArticleToArticleGroup] CHECK CONSTRAINT [FK_ArticleToArticleGroup_Article]
    ALTER TABLE [dbo].[ArticleToArticleGroup]  WITH CHECK ADD  CONSTRAINT [FK_ArticleToArticleGroup_ArticleGroup] FOREIGN KEY([ArticleGroupID])
    REFERENCES [dbo].[ArticleGroup] ([ID])
    ALTER TABLE [dbo].[ArticleToArticleGroup] CHECK CONSTRAINT [FK_ArticleToArticleGroup_ArticleGroup]
  • Avatar
    Andrzej Bakun

    I am interested in many-to-many and payload feature. Can this payload property be used for any kind of filtering? I have a scenario where my join table also contains From/To fields. Any join is valid in a certain period of time. Can this feature be used here for that kind of filtering without creating intermediate joining entity?

    • Jeremy Likness
      Jeremy LiknessMicrosoft logo

      Yes, payload can be used in filtering. Depending on your configuration, you just need to determine how to get that join entity.

      If there is navigation to the join entity the navigation can be used, or the DbSet of the join entity can be used in a query with a manual join.
      ​The payload property in the join entity can be accessed via indexer (since it’s implemented as a dictionary) or by using the EF.Property method.