Highlights
Many-to-many is working now in the daily builds!
We're still finishing off support, but common cases are working now. Let's walk through a simple end-to-end example.
Here are our 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 requied in OnModelCreating
. For example, here's our DbContext:
public class BlogContext : DbContext{ public DbSet<Post> Posts { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .LogTo(Console.WriteLine, LogLevel.Information) .EnableSensitiveDataLogging() .UseSqlite("Data Source = test.db");}
The DbSet for Posts is all EF Core needs to discover Post and Tag and their many-to-many relationship.
When Migrations (or EnsureCreated
) are used to create the database, EF Core will automatically create the join table. For example, on SQLite for this model, EF Core generates:
CREATE TABLE "Posts" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT, "Name" TEXT NULL);CREATE TABLE "Tag" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_Tag" PRIMARY KEY AUTOINCREMENT, "Text" TEXT NULL);CREATE TABLE "PostTag" ( "Post_Id" INTEGER NOT NULL, "Tag_Id" INTEGER NOT NULL, CONSTRAINT "PK_PostTag" PRIMARY KEY ("Post_Id", "Tag_Id"), CONSTRAINT "FK_PostTag_Posts_Post_Id" FOREIGN KEY ("Post_Id") REFERENCES "Posts" ("Id") ON DELETE CASCADE, CONSTRAINT "FK_PostTag_Tag_Tag_Id" FOREIGN KEY ("Tag_Id") REFERENCES "Tag" ("Id") ON DELETE CASCADE);
(We plan to remove the underscores from the key names here.)
Let's write a small console app to save and query some entities:
public static class Program{ public static void Main() { using (var context = new BlogContext()) { context.Database.EnsureDeleted(); context.Database.EnsureCreated(); 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(); } using (var context = new BlogContext()) { 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}'"); } Console.WriteLine(); } } }}
In this example, first three Tags are created and these are added to the Post.Tags
collection. Notice that the same Tag can be related to many different Posts, and vice-versa. EF Core generates the following SQL when SaveChanges is called:
INSERT INTO "Posts" ("Name")VALUES (@p0);SELECT "Id"FROM "Posts"WHERE changes() = 1 AND "rowid" = last_insert_rowid();INSERT INTO "Posts" ("Name")VALUES (@p0);SELECT "Id"FROM "Posts"WHERE changes() = 1 AND "rowid" = last_insert_rowid();INSERT INTO "Posts" ("Name")VALUES (@p0);SELECT "Id"FROM "Posts"WHERE changes() = 1 AND "rowid" = last_insert_rowid();INSERT INTO "Tag" ("Text")VALUES (@p0);SELECT "Id"FROM "Tag"WHERE changes() = 1 AND "rowid" = last_insert_rowid();INSERT INTO "Tag" ("Text")VALUES (@p0);SELECT "Id"FROM "Tag"WHERE changes() = 1 AND "rowid" = last_insert_rowid();INSERT INTO "Tag" ("Text")VALUES (@p0);SELECT "Id"FROM "Tag"WHERE changes() = 1 AND "rowid" = last_insert_rowid();INSERT INTO "PostTag" ("Post_Id", "Tag_Id")VALUES (@p1, @p2);INSERT INTO "PostTag" ("Post_Id", "Tag_Id")VALUES (@p0, @p1);INSERT INTO "PostTag" ("Post_Id", "Tag_Id")VALUES (@p0, @p1);INSERT INTO "PostTag" ("Post_Id", "Tag_Id")VALUES (@p0, @p1);INSERT INTO "PostTag" ("Post_Id", "Tag_Id")VALUES (@p0, @p1);INSERT INTO "PostTag" ("Post_Id", "Tag_Id")VALUES (@p0, @p1);
Notice that EF Core first inserts the Posts and Tags, then creates and inserts rows into the join table for the relationships.
Finally, the application creates a new DbContext and queries back the Posts and their related Tags. The SQL generated is:
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']SELECT "p"."Id", "p"."Name", "t0"."Post_Id", "t0"."Tag_Id", "t0"."Id", "t0"."Text"FROM "Posts" AS "p"LEFT JOIN ( SELECT "p0"."Post_Id", "p0"."Tag_Id", "t"."Id", "t"."Text" FROM "PostTag" AS "p0" INNER JOIN "Tag" AS "t" ON "p0"."Tag_Id" = "t"."Id") AS "t0" ON "p"."Id" = "t0"."Post_Id"ORDER BY "p"."Id", "t0"."Post_Id", "t0"."Tag_Id", "t0"."Id"
Notice that the query automatically uses the join table to bring back all related Tags.
This demo just scratches the surface of what can be done with many-to-many relationships. For example, look for future content that shows how to introduce a join table payload while still continuing to use the relationship as many-to-many!
EF Core 5.0 preview 7
EF Core 5.0 preview 7 is available on NuGet now!
New features in preview 7 include:
- Register a factory for DbContext instances in D.I.
- Clear all tracked entities to reset DbContext state
- New pattern for store-generated defaults
- Better support for Cosmos partition keys
- New Cosmos configuration options
- Scaffold-DbContext now singularizes
- Support for database savepoints
Remember that all these features from previous previews are also included in preview 7:
- Split queries for Include and projection of collections
- IndexAttribute
- Improved query translation exceptions
- Application-specified transaction IDs
- IPAddress mapping
- Option to exclude OnConfiguring when scaffolding
- Translations for FirstOrDefault, etc. on strings
- Simplification of case blocks in generated SQL
- Database collations
- Flow arguments into IDesignTimeDbContextFactory
- No-tracking queries with identity resolution
- Stored (persisted) computed columns
- SQLite computed columns
- Configure database precision/scale in model
- Specify SQL Server index fill factor
- Filtered Include
- Command-line parameters for namespaces when scaffolding a DbContext or generating migrations
- Command-line support for passing a connection string to [dotnet ef database update]/[Update-Database]
- EnableDetailedErrors provides more information in query mapping exceptions
- Improved support for using Cosmos partition keys in queries
- New ModelBuilder API for navigation properties
- Support for the SQL Server DATALENGTH function
- Use a C# attribute to specify a property backing field
- Complete discriminator mapping
- Performance improvements in Microsoft.Data.Sqlite
- Simple Logging
- Simple way to get generated SQL
- Use a C# attribute to indicate that an entity has no key
- Connection or connection string can be changed on initialized DbContext
- Change-tracking proxies
- Enhanced debug views
- Improved handling of database null semantics
- Indexer properties
- Generation of check constraints for enum mappings
- IsRelational method for differential model building
- Cosmos optimistic concurrency with ETags
- Query translations for more DateTime constructs
- Query translations for more byte array constructs
- Query translation for Reverse
- Query translation for bitwise operators
- Query translation for strings on Cosmos
The .NET Blog announcement has installation instructions and full details.
EF Core 3.1.6
EF Core 3.1.6 is available on NuGet now.
This is a patch release of 3.1 containing important bug fixes.
EF Core 5.0
We have completed a re-balancing of the work for EF Core 5.0. The major changes are:
- The full implementation of many-to-many has been pulled into EF Core 5.0. (Previously we were only planning for many-to-many navigations.)
- Reintroduction of split Includes has become a major feature for 5.0.
- Some of the smaller, lower priority enhancements and bug fixes have been punted to allow for these changes.
- The plan for architectural/provider documentation was too ambitious. We still believe this is important, but unfortunately it won't land with EF Core 5.0.
The plan at EF Core 5.0 plan as been updated to reflect this. Feedback is still greatly appreciated. As always, this plan is not set-in-stone; we expect it will evolve throughout the release cycle as we learn.
Each high-level theme for EF Core 5.0 is linked in the table below together with its current high-level status.
EF Core 5.0 Theme | Status |
---|---|
Many-to-many navigation properties (a.k.a "skip navigations") | In-progress |
Full many-to-many support | In-progress |
Table-per-type (TPT) inheritance mapping | Done |
Filtered Include | Done |
Split Includes | Done |
Rationalize ToTable, ToQuery, ToView, FromSql, etc. | Done |
General query enhancements | In-progress |
Migrations and deployment experience | In-progress |
EF Core platforms experience | In-progress |
Performance | In-progress |
Architectural/contributor documentation | Cut |
Microsoft.Data.Sqlite documentation | Done |
General documentation | In-progress |
Fixing bugs | In-progress |
Small enhancements | In-progress |
Pull requests from the last week
Community contributions
- @ChristopherHaws fixed a bug where properties with
ValueGeneratedOnAdd
are reseeded in every migration
Many thanks to all our contributors!
EF Core
- Model building
- Add public APIs to configure shared type entities
- Add API to allow configuration of the association entity type
- Log an error for Required on principal to dependent
- Set default max batch size for SQL Server
- Allow mapping entity types to SQL queries
- Reduce allocations in EntityType by short-circuiting
- Throw for shared type in inheritance
- Add a warning for decimal keys for SqlServer
- Remove IIndexNameChangedConvention
- Remove non-named shared type ModelBuilder overloads
- Rename IsEagerLoaded to AutoInclude
- Return void from AddShared and AddOwned
- Consistently refer to association entities as join entities
- Rename SharedEntity to SharedTypeEntity
- Deprecate defining query at Core level
- Query
- Fix small perf regression in query compilation
- Fix to apply pending selector on single result query before trying to expand collection navigation
- Fix to stop reading default value from databases
- Use correct column mappings for TVF/FromSql
- Disallow FromSql/TVF with TPT
- Make SqlExpression.Type to be reference type or non-nullable value type
- Fix incorrect results returned when joining a key-less view to an entity
- Use object.Equals internally in query for key comparison
- Regression test for a sub-query inside a projection from a filtered Queryable
- Reenable some query tests
- Regression test for left joins with Include
- Documentation
- Change tracking
- Miscellaneous
- Scaffolding
- Migrations
- Correctly generate null literals in arrays
- Save Changes
- Fix savepoint bugs
Burn-down for EF Core 5.0
This is the burn-down chart we use internally to track progress towards EF Core 5.0.
- The gray bar at the top represents all the issues already fixed for 5.0
- The 'Closed' category only includes issues that have been fixed, not issues closed for any other reason.
- The 'Fixed' category includes issues that have been fixed, but where the change has not yet been merged.
- We're aiming to be feature-complete by the beginning of September.
Builds to use
- The daily builds are the most up-to-date available.
- Preview: EF Core 5.0 preview 7
- Using the daily builds or previews is a great way to find issues and provide feedback as early as possible. The sooner we get such feedback, the more likely it will be actionable before the next official release.
- Current and LTS: EF Core 3.1.6
Releases
See EF Core releases and planning (Roadmap) in our documentation for full details.
More Information
See the top of this issue for links to more information.
Comments are disabled on this issue to reduce noise. Please use the related discussion issue for any comments on these status updates.