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

时间:2018-01-26 13:56:49

标签: c# entity-framework-core

我只能包含相关实体。

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

但是,我不需要整个BlogPost实体。我只对特定属性感兴趣,例如:

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

4 个答案:

答案 0 :(得分:3)

您可以使用Include加载整个实体,也可以将所需内容投射到.Select

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

或者,您可以这样做:

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

当您不需要整个孩子时,Select总是更好,因为它可以防止查询不需要的数据。

答案 1 :(得分:2)

实际上你想要的是:在一个共同的,代表性的部分和一个你并不总是想从数据库中提取的特殊部分中拆分一个实体。这不是一个罕见的要求。考虑产品和图像,文件及其内容,或拥有公共和私人数据的员工。

实体框架核心支持两种实现此目的的方法:拥有类型和表分割。

拥有类型

拥有类型是包含在另一种类型中的类型。它只能通过其所有者访问。这就是它的样子:

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

自有型映射:

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

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

但是,根据docs

  

在查询所有者时,默认情况下将包含所拥有的类型。

这意味着像......

这样的陈述
var posts = context.Posts.ToList();

...将始终为您发送的内容。因此,拥有类型可能不适合您。我仍然提到它,因为我发现当PostsIncluded时...

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

...拥有的类型PostContent包括在内(免责声明:我不确定这是一个错误还是一个功能......)。在这种情况下,当应包含所拥有的类型时,需要ThenInclude

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

因此,如果始终通过Post查询Blog s,则拥有的类型可能是合适的。

我不认为这适用于此,但是当拥有类型的孩子与其父母有识别关系时,它会这样做(经典示例:Order-OrderLine)。

表拆分

使用表拆分,数据库表将拆分为两个或更多实体。或者,从对象方面:两个或多个实体映射到一个表。该模型几乎完全相同。唯一的区别是,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; }
}

表拆分映射:

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");

现在{@ 1}}将始终在没有内容的情况下被查询。 Post应始终PostContent - 明确。

此外,现在可以在没有所有者Include()的情况下查询PostContent

Post

我认为这正是您所寻找的。

当然,如果你想在没有内容的情况下获取帖子时总是使用投影,那么你可以不用这些映射。

答案 2 :(得分:0)

你可以试试这个:

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

修改
如果你想坚持你的数据模型,你可以尝试这样的事情:

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();
}

答案 3 :(得分:0)

我认为有一种更简单的方法可以做到这一点。投影很好,但是如果您想要来自父实体的所有列而其中大部分来自子实体怎么办?当这些类型具有很多属性时,使用投影意味着您需要编写大量代码行来选择您想要的所有内容,除了少数您不需要的。好吧,由于使用投影意味着不会跟踪您的实体,因此使用 .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;
    }
}