Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to include only selected properties on related entities

I can include only related entities.

using (var context = new BloggingContext()) 
{ 
    // Load all blogs, all related posts
    var blogs1 = context.Blogs 
                       .Include(b => b.Posts) 
                       .ToList(); 
}

However, I don't need entire BlogPost entity. I'm interested only in particular properties, e.g:

using (var context = new BloggingContext()) 
{ 
    // Load all blogs, all and titles of related posts
    var blogs2 = context.Blogs 
                       .Include(b => b.Posts.Select(p => p.Title) //throws runtime exeption
                       .ToList(); 

    foreach(var blogPost in blogs2.SelectMany(b => b.Posts))
    {
        Console.Writeline(blogPost.Blog.Id); //I need the object graph
        Console.WriteLine(blogPost.Title); //writes title
        Console.WriteLine(blogPost.Content); //writes null
    }
}
like image 333
Liero Avatar asked Jan 26 '18 13:01

Liero


People also ask

How do I select specific columns in Entity Framework?

We can do that simply by using the “new” operator and selecting the properties from the object that we need. In this case, we only want to retrieve the Id and Title columns. There.

What is .include in Entity Framework?

Entity Framework Classic Include The Include method lets you add related entities to the query result. In EF Classic, the Include method no longer returns an IQueryable but instead an IncludeDbQuery that allows you to chain multiple related objects to the query result by using the AlsoInclude and ThenInclude methods.

How you can load related entities in EF?

Entity Framework supports three ways to load related data - eager loading, lazy loading and explicit loading. The techniques shown in this topic apply equally to models created with Code First and the EF Designer.

What is include and ThenInclude in Entity Framework?

Entity Framework Classic ThenIncludeThe ThenInclude method moves the chaining level to the property included. It allows us to include related objects from the next level. ThenInclude is a syntactic sugar method to make it easier and clearer to include multiple related objects.


2 Answers

You either use Include which loads the entire entity, or you project what you need to a .Select:

var blogs2 = context.Blogs 
    .Select(x => new 
    {
        BlogName = x.BlogName, //whatever
        PostTitles = x.Post.Select(y => y.Title).ToArray()
    }) 
   .ToList(); 

Or, you could do something like this:

var blogs2 = context.Blogs 
    .Select(x => new 
    {
        Blog = x,
        PostTitles = x.Post.Select(y => y.Title).ToArray()
    }) 
   .ToList(); 

A Select is always better when you don't need the entire child, as it prevents querying unneeded data.

like image 133
Camilo Terevinto Avatar answered Sep 28 '22 08:09

Camilo Terevinto


In fact what you want is: split an entity in a common, representational part and a special part that you don't always want to pull from the database. This is not an uncommon requirement. Think of products and images, files and their content, or employees with public and private data.

Entity framework core supports two ways to achieve this: owned type and table splitting.

Owned type

An owned type is a type that's wrapped in another type. It can only be accessed through its owner. This is what it looks like:

public class Post
{
    public int ID { get; set; }
    public Blog Blog { get; set; }
    public string Title { get; set; }
    public PostContent Content { get; set; }
}

public class PostContent
{
    public string Content { get; set; }
}

And the owned-type mapping:

modelBuilder.Entity<Post>().OwnsOne(e => e.Content);

Where Blog is

public class Blog
{
    public Blog()
    {
        Posts = new HashSet<Post>();
    }
    public int ID { get; set; }
    public string Name { get; set; }

    public ICollection<Post> Posts { get; set; }
}

However, as per the docs:

When querying the owner the owned types will be included by default.

Which means that a statement like...

var posts = context.Posts.ToList();

...will always get you posts and their contents. Therefore, owned type is probably not the right approach for you. I still mentioned it, because I found out that when Posts are Included...

var blogs = context.Blogs.Include(b => b.Posts).ToList();

...the owned types, PostContents, are not included (DISCLAIMER: I'm not sure if this is a bug or a feature...). In this case, when the owned types should be included a ThenInclude is required:

var blogs = context.Blogs.Include(b => b.Posts)
        .ThenInclude(p => p.Content).ToList();

So if Posts will always be queried through Blogs, owned type may be appropriate.

I don't think this applies here, but it does when children having owned types have an identifying relationship with their parents (classical example: Order-OrderLine).

Table splitting

With table splitting a database table is split up into two or more entities. Or, from the objects side: two or more entities are mapped to one table. The model is almost identical. The only difference is that PostContent now has a required primary key property (ID, of course having the same value as Post.ID):

public class Post
{
    public int ID { get; set; }
    public Blog Blog { get; set; }
    public string Title { get; set; }
    public PostContent Content { get; set; }
}

public class PostContent
{
    public int ID { get; set; }
    public string Content { get; set; }
}

And the table-splitting mapping:

modelBuilder.Entity<Post>()
    .HasOne(e => e.Content).WithOne()
    // or .WithOne(c => c.Post) if there is a back reference
    .HasForeignKey<PostContent>(e => e.ID);
modelBuilder.Entity<Post>().ToTable("Posts");
modelBuilder.Entity<PostContent>().ToTable("Posts");

Now Posts will always be queried without their contents by default. PostContent should always be Include()-ed explicitly.

Also, PostContent can now be queried without its owner Post:

var postContents = context.Set<PostContent>().ToList();

I think this is exactly what you're looking for.

Of course you can do without these mappings if you'll always use projections when you want to fetch posts without contents.

like image 27
Gert Arnold Avatar answered Sep 28 '22 06:09

Gert Arnold