且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2023-02-26 19:54:54

事实上,您想要的是:将一个实体拆分为一个公共的、代表性的部分和一个您并不总是想从数据库中提取的特殊部分.这并不是一个罕见的要求.想想产品和图像、文件及其内容,或者拥有公共和私人数据的员工.

实体框架核心支持两种方式来实现:拥有类型和表拆分.

自有类型

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

公共类Post{公共 int ID { 获取;放;}公共博客博客{得到;放;}公共字符串标题{获取;放;}公共 PostContent 内容 { get;放;}}公共类 PostContent{公共字符串内容{获取;放;}}

以及拥有的类型映射:

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

博客在哪里

公开课博客{公共博客(){Posts = new HashSet();}公共 int ID { 获取;放;}公共字符串名称 { 获取;放;}公共 ICollection<Post>帖子{得到;放;}}

但是,根据 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();

因此,如果 Post 总是通过 Blog 进行查询,则拥有的类型可能是合适的.

我不认为这在这里适用,但是当拥有类型的孩子与他们的父母有识别关系时它就适用(经典示例:Order-OrderLine).

表拆分

通过表拆分,数据库表被拆分为两个或多个实体.或者,从对象方面:两个或多个实体映射到一个表.模型几乎相同.唯一的区别是 PostContent 现在有一个必需的主键属性(ID,当然与 Post.ID 具有相同的值):

公共类Post{公共 int ID { 获取;放;}公共博客博客{得到;放;}公共字符串标题{获取;放;}公共 PostContent 内容 { get;放;}}公共类 PostContent{公共 int ID { 获取;放;}公共字符串内容{获取;放;}}

以及分表映射:

modelBuilder.Entity().HasOne(e => e.Content).WithOne()//或 .WithOne(c => c.Post) 如果有反向引用.HasForeignKey(e => e.ID);modelBuilder.Entity().ToTable("Posts");modelBuilder.Entity().ToTable(Posts");

现在 Post 将始终在默认情况下被查询而没有它们的内容.PostContent 应该始终是 Include() 明确的.

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

var postContents = context.Set().ToList();

我认为这正是您要找的.​​p>

当然,如果您想获取没有内容的帖子时始终使用投影,则可以不使用这些映射.

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

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.