配置好一对多关系后,就可以在 C# 代码里使用了。在添加数据时,不需要同时给双方的导航属性赋值,只需要赋值某一方的导航属性,然后将这一方加入 DbContext 即可。简单来说,就是只要能够从被加入的实体中检测到对方,EFCore 就会自动将对方也加入数据库。比如

Post post = new Post() { Title = "文章标题", Content = "文章内容" };
Blog blog = new Blog() { Name = "John" };
post.Blog = blog;
ctx.Add(post);
await ctx.SaveChangesAsync();

这段代码只添加了 post,但 blog 也会被加入到数据库,因为在 post 的导航属性里能找到它。但如果这样:

Post post = new Post() { Title = "文章标题", Content = "文章内容" };
Blog blog = new Blog() { Name = "John" };
post.Blog = blog;
ctx.Add(blog);
await ctx.SaveChangesAsync();

虽然从 post 里可以找到 blog,但是由于添加的是 blog,EFCore 接收到的也只有 blog,所以 post 是不会被加入的。如果要添加 blog,应该这么写:

Post post = new Post() { Title = "文章标题", Content = "文章内容" };
Blog blog = new Blog() { Name = "John" };
blog.Posts.Add(post);
ctx.Add(blog);
await ctx.SaveChangesAsync();

注意这里需要确保 Posts 不是 null,否则会异常。建议在实体类中为集合类导航属性设置初始值。

那么如何获取一个实体的关系实体呢?如果只是像下面这样直接访问导航属性的话是不行的。

Blog blog = ctx.Blogs.FirstOrDefault(e => e.Name == "John");
foreach (var post in blog.Posts)
{
    Console.WriteLine(post.Title);
}

这里 Posts 里并没有数据,这是因为 EFCore 为了性能,在一般的查询中是不会顺带获取关系实体的。上面的代码只执行了下面一行 SQL 语句:

SELECT TOP(1) [t].[Id], [t].[Name]
FROM [T_Blogs] AS [t]
WHERE [t].[Name] = N'John'

实体之间的关系在数据库层面其实只是一列外键,要获取外键数据的话需要在 SQL 里使用 JOIN 查询。而在 C# 代码中,则需要程序员通过 Include 方法显式启用:

Blog blog = ctx.Blogs.Include(e => e.Posts).FirstOrDefault(e => e.Name == "John");
foreach (var post in blog.Posts)
{
    Console.WriteLine(post.Title);
}

只需在 Include 方法里传入导航属性即可,EFCore 会追加查询。生成的 SQL 语句:

SELECT [t0].[Id], [t0].[Name], [t1].[Id], [t1].[BlogId], [t1].[Content], [t1].[Title]
FROM (
    SELECT TOP(1) [t].[Id], [t].[Name]
    FROM [T_Blogs] AS [t]
    WHERE [t].[Name] = N'John'
) AS [t0]
LEFT JOIN [T_Posts] AS [t1] ON [t0].[Id] = [t1].[BlogId]
ORDER BY [t0].[Id], [t1].[Id]

获取到关系实体后就很方便了。从 Post 获取 Blog 的代码如下:

var posts = ctx.Posts.Where(e => e.Blog.Name == "John");
foreach (var post in posts)
{
    Console.WriteLine(post.Title);
}

注意这里并没有 Include 也能获取到,这是因为我们对 Blog 的引用是在 Where 这个查询语句里的,EFCore 会自动帮我们查询关系实体。但如果我们在查询语句外引用 Blog 依旧会报错,下面这样的代码会给出异常:

var posts = ctx.Posts.Where(e => e.Blog.Name == "John");
foreach (var post in posts)
{
    Console.WriteLine(post.Blog.Name);
}

总结一句话就是:查询语句内引用无需 Include,查询语句外引用需要 Include。