配置好一对多关系后,就可以在 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。
[…] 【EFCore】笔记10 关系配置2 […]