Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding docs for filtered include feature and updating query samples #2260

Merged
merged 1 commit into from
Apr 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions entity-framework/core/querying/related-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,27 @@ You may want to include multiple related entities for one of the entities that i
> [!CAUTION]
> Since version 3.0.0, each `Include` will cause an additional JOIN to be added to SQL queries produced by relational providers, whereas previous versions generated additional SQL queries. This can significantly change the performance of your queries, for better or worse. In particular, LINQ queries with an exceedingly high number of `Include` operators may need to be broken down into multiple separate LINQ queries in order to avoid the cartesian explosion problem.

### Filtered include

> [!NOTE]
> This feature is introduced in EF Core 5.0.

When applying Include to load related data, you can apply certain enumerable operations on the included collection navigation, which allows for filtering and sorting of the results.

Supported operations are: `Where`, `OrderBy`, `OrderByDescending`, `ThenBy`, `ThenByDescending`, `Skip`, and `Take`.

Such operations should be applied on the collection navigation in the lambda passed to the Include method, as shown in example below:

[!code-csharp[Main](../../../samples/core/Querying/RelatedData/Sample.cs#FilteredInclude)]

Each included navigation allows only one unique set of filter operations. In cases where multiple Include operations are applied for a given collection navigation (`blog.Posts` in the examples below), filter operations can only be specified on one of them:

[!code-csharp[Main](../../../samples/core/Querying/RelatedData/Sample.cs#MultipleLeafIncludesFiltered1)]

Alternatively, identical operations can be applied for each navigation that is included multiple times:

[!code-csharp[Main](../../../samples/core/Querying/RelatedData/Sample.cs#MultipleLeafIncludesFiltered2)]

### Include on derived types

You can include related data from navigations defined only on a derived type using `Include` and `ThenInclude`.
Expand Down
58 changes: 58 additions & 0 deletions samples/core/Querying/BloggingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,64 @@ public class BloggingContext : DbContext
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(b => b.Posts)
.WithOne(p => p.Blog)
.OnDelete(DeleteBehavior.NoAction);

modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.Tags)
.HasForeignKey(pt => pt.PostId);

modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.Posts)
.HasForeignKey(pt => pt.TagId);

modelBuilder.Entity<Blog>()
.HasData(
new Blog { BlogId = 1, Url = @"https://devblogs.microsoft.com/dotnet", Rating = 5, OwnerId = 1, },
new Blog { BlogId = 2, Url = @"https://mytravelblog.com/", Rating = 4, OwnerId = 3 });

modelBuilder.Entity<Post>()
.HasData(
new Post { PostId = 1, BlogId = 1, Title = "What's new", Content = "Lorem ipsum dolor sit amet", Rating = 5, AuthorId = 1 },
new Post { PostId = 2, BlogId = 2, Title = "Around the World in Eighty Days", Content = "consectetur adipiscing elit", Rating = 5, AuthorId = 2 },
new Post { PostId = 3, BlogId = 2, Title = "Glamping *is* the way", Content = "sed do eiusmod tempor incididunt", Rating = 4, AuthorId = 3 },
new Post { PostId = 4, BlogId = 2, Title = "Travel in the time of pandemic", Content = "ut labore et dolore magna aliqua", Rating = 3, AuthorId = 3 });

modelBuilder.Entity<Person>()
.HasData(
new Person { PersonId = 1, Name = "Dotnet Blog Admin", PhotoId = 1 },
new Person { PersonId = 2, Name = "Phileas Fogg", PhotoId = 2 },
new Person { PersonId = 3, Name = "Jane Doe", PhotoId = 3 });

modelBuilder.Entity<PersonPhoto>()
.HasData(
new PersonPhoto { PersonPhotoId = 1, Caption = "SN", Photo = new byte[] { 0x00, 0x01 } },
new PersonPhoto { PersonPhotoId = 2, Caption = "PF", Photo = new byte[] { 0x01, 0x02, 0x03 } },
new PersonPhoto { PersonPhotoId = 3, Caption = "JD", Photo = new byte[] { 0x01, 0x01, 0x01 } });

modelBuilder.Entity<Tag>()
.HasData(
new Tag { TagId = "general" },
new Tag { TagId = "classic" },
new Tag { TagId = "opinion" },
new Tag { TagId = "informative" });

modelBuilder.Entity<PostTag>()
.HasData(
new PostTag { PostTagId = 1, PostId = 1, TagId = "general" },
new PostTag { PostTagId = 2, PostId = 1, TagId = "informative" },
new PostTag { PostTagId = 3, PostId = 2, TagId = "classic" },
new PostTag { PostTagId = 4, PostId = 3, TagId = "opinion" },
new PostTag { PostTagId = 5, PostId = 4, TagId = "opinion" },
new PostTag { PostTagId = 6, PostId = 4, TagId = "informative" });
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;ConnectRetryCount=0");
Expand Down
38 changes: 37 additions & 1 deletion samples/core/Querying/RelatedData/Sample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public static void Run()
.ToList();
}
#endregion

#region Eager
using (var context = new BloggingContext())
{
Expand Down Expand Up @@ -128,6 +128,42 @@ public static void Run()
.ToList();
}
#endregion

#region FilteredInclude
using (var context = new BloggingContext())
{
var filteredBlogs = context.Blogs
.Include(blog => blog.Posts
.Where(post => post.BlogId == 1)
.OrderByDescending(post => post.Title)
.Take(5))
.ToList();
}
#endregion

#region MultipleLeafIncludesFiltered1
using (var context = new BloggingContext())
{
var filteredBlogs = context.Blogs
.Include(blog => blog.Posts.Where(post => post.BlogId == 1))
.ThenInclude(post => post.Author)
.Include(blog => blog.Posts)
.ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
.ToList();
}
#endregion

#region MultipleLeafIncludesFiltered2
using (var context = new BloggingContext())
{
var filteredBlogs = context.Blogs
.Include(blog => blog.Posts.Where(post => post.BlogId == 1))
.ThenInclude(post => post.Author)
.Include(blog => blog.Posts.Where(post => post.BlogId == 1))
.ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
.ToList();
}
#endregion
}
}
}