简体   繁体   English

如何仅包含相关实体的选定属性

[英]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.但是,我不需要整个 BlogPost 实体。 I'm interested only in particular properties, eg:我只对特定的属性感兴趣,例如:

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
    }
}

You either use Include which loads the entire entity, or you project what you need to a .Select :您要么使用Include加载整个实体,要么将您需要的内容投影到.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.当您不需要整个孩子时, Select总是更好,因为它可以防止查询不需要的数据。

You can try this :你可以试试这个:

using (var context = new BloggingContext())
{
    var blogProps = context.Blogs
        .SelectMany(b => 
            b.Posts.Select(p => 
                new { Blog = b, PostTitle = p.Title }
            )
         )
        .ToList();
}

EDIT编辑
If you want to stick to your data model, you could try something like this :如果你想坚持你的数据模型,你可以尝试这样的事情:

using (var context = new BloggingContext())
{
    var blogProps = context.Blogs
        .Select(b => 
            new Blog 
            { 
                Name = b.Name, 
                Posts = new List<Post>(b.Posts.Select(p => 
                    new Post 
                    { 
                        Title = p.Title 
                    })
            }
        )
        .ToList();
}

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 Blog在哪里

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 ...我还是提到了,因为我发现当Posts are Included ...

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

...the owned types, PostContent s, are not included (DISCLAIMER: I'm not sure if this is a bug or a feature...). ...拥有的类型PostContent包括在内(免责声明:我不确定这是错误还是功能......)。 In this case, when the owned types should be included a ThenInclude is required:在这种情况下,当应包含拥有的类型时,需要ThenInclude

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

So if Post s will always be queried through Blog s, owned type may be appropriate.因此,如果Post总是通过Blog查询,则拥有类型可能是合适的。

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 ).我不认为这在这里适用,但是当拥有类型的孩子与他们的父母有识别关系时(经典示例: 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 ):唯一的区别是PostContent现在有一个必需的主键属性( ID ,当然与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 Post s will always be queried without their contents by default.现在,默认情况下Post将始终在没有其内容的情况下进行查询。 PostContent should always be Include() -ed explicitly. PostContent应该始终是Include()显式。

Also, PostContent can now be queried without its owner Post :此外,现在可以在没有所有者Post情况下查询PostContent

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.当然,如果您在想要获取没有内容的帖子时始终使用投影,则可以不使用这些映射。

I think there's a much easier way to do this.我认为有一种更简单的方法可以做到这一点。 Projection is nice and all, but what if you want all the columns from your parent entity and most of them from the child?投影很好,但是如果您想要来自父实体的所有列而其中大部分来自子实体怎么办? When those types have a lot of properties, using projection means you have a lot of lines of code to write just to select everything you want except the few that you don't.当这些类型具有很多属性时,使用投影意味着您需要编写大量代码行来选择您想要的所有内容,除了少数您不需要的。 Well, since using projection means your entities won't be tracked, it's much easier to use .AsNoTracking() and then just empty out the things you don't want.好吧,因为使用投影意味着您的实体不会被跟踪,所以使用.AsNoTracking()然后清空您不想要的东西要容易得多。

var foos = await _context.DbSet<Foo>()
    .AsQueryable()
    .Where(x => x.Id == id)
    .Include(x => x.Bars)
    .AsNoTracking()
    .ToListAsync();

foreach (var foo in foos)
{
    foreach (Bar bar in foo.Bars)
    {
        bar.Baz = null;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM