【问题标题】:How to include only selected properties on related entities如何仅在相关实体上包含选定的属性
【发布时间】:2018-01-26 13:56:48
【问题描述】:

我只能包含相关实体。

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

【问题讨论】:

  • 你在使用实体框架核心吗?
  • 是的,最新的稳定版本

标签: c# entity-framework-core


【解决方案1】:

您要么使用加载整个实体的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 总是更好,因为它可以防止查询不需要的数据。

【讨论】:

  • 感谢您的建议。使用匿名类型的自定义选择是最简单的方案,但我需要博客实体和它的对象图
  • @Liero 那么你必须加载整个实体
【解决方案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();

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

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

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

我认为这不适用于这里,但当拥有自己的类型的孩子与他们的父母有识别关系时(经典示例: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");

现在Posts 将始终在默认情况下不包含其内容进行查询。 PostContent 应始终明确为 Include()-ed。

另外,PostContent 现在可以在没有其所有者Post 的情况下进行查询:

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

我认为这正是您想要的。

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

【讨论】:

    【解决方案3】:

    你可以试试这个:

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

    【讨论】:

    • 好的,但我实际上也需要博客实体。我知道我可以使用新的匿名类型来解决它,但我很好奇它是否可以那样工作
    • 如果您也需要博客,您可以将其添加到匿名类型。您是否尝试以另一种方式做到这一点?
    • 或者您可能不想使用匿名类型并坚持您的数据模型?所以你想让一些属性为空?
    • 是的,如果可能的话,我想坚持我的数据模型
    • @Liero 你可以,但工作量很大。您基本上必须使用匿名构造函数自己构建每个实体,例如 new Blog { Name = b.Name }
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多