EF Core 关联数据

开发环境

ASP.NET Core 3.0 + Entity Framework 3.0

正文

Entity Framework Core 通过实体类的导航属性来加载相关数据。有三种常见的方式:

预先加载 - 将关联数据作为查询的一部分一起查询出来。

显示加载 - 查询主数据之后,再从数据库查询相关数据。

延迟加载 - 在访问导航属性的时候,再从数据库查询相关数据。

预先加载

预先加载又叫贪婪加载,使用Include方法来指定需要查询的相关数据。如下所示:

using (var context = new BlogContext())
{
var blogs = await context.Article.Where(s => s.Status == 1)
.Include(article => article.Remarks) //加载文章评论
.ThenInclude(remark => remark.User) //加载评论者
.Include(article => article.Remarks)
.ThenInclude(remark => remark.RemarkReplys) //加载评论的回复
.ThenInclude(remarkReply => remarkReply.User) //加载评论回复的回复者
.Include(article => article.Remarks)
.ThenInclude(remark => remark.RemarkReplys)
.ThenInclude(remarkReply => remarkReply.TargetUser) //加载评论回复目标人
.Include(article => article.ArticleMusics) //加载文章音乐
.ThenInclude(articleMusic => articleMusic.Music) //加载文章音乐对应的音乐
.FirstOrDefaultAsync(s => s.Id == id);
}

多关联:可以使用多个Include加载多个关联数据。如上代码所述,加载了文章关联的评论和音乐。

多层级:可以在Include后使用ThenInclude加载更深级别的关联数据(关联数据的关联数据)。如上代码所述,加载了评论关联的回复以及回复关联的回复人等。

合并查询:可以看到,上述代码示例中使用了多次Include(article => article.Remarks),一般情况下,EF在生成SQL时合并相应的关联查询,所以不用担心查询冗余。

此外,EF还有一种自动填充导航属性的功能,如果当前数据上下文对象中已经加载过相关联的数据,即使不使用Include,导航属性也可能会被填充,如:

var articleMusic = await context.ArticleMusic
.Include(s=>s.Music)
.FirstOrDefaultAsync(s => s.ArticleId == id); //在Include之前已经加载过文章对应的音乐
 
var article = await context.Article.Where(s => s.Status == 1)
//.Include(article => article.ArticleMusics) //加载文章音乐
// .ThenInclude(articleMusic => articleMusic.Music) //加载文章音乐对应的音乐
.FirstOrDefaultAsync(s => s.Id == id); //即使注释掉,article对象的ArticleMusic以及ArticleMusic对象的Music属性仍然会被加载

显式加载

显式加载使用Entry方法来进行相关数据的加载。此方式在EF Core 1.1才能使用。

using (var context = new BlogContext())
{
var article = await context.Article.Where(s => s.Status == 1)
.FirstOrDefaultAsync(s => s.Id == id);
//加载文章评论
context.Entry(article).Collection(article => article.Remarks).Load();
}

这种方式我个人不太常用,具体使用场景需要各位自己探索。

延迟加载

延迟加载在读取数据时不会加载关联数据,在之后需要用到关联数据的时候会自动加载关联数据,是EF 6就已经有的功能,在EF Core 2.1中得到支持。要使用延迟加载功能,

需要安装 Microsoft.EntityFrameworkCore.Proxies 包,并通过UseLazyLoadingProxies来启用,如:

optionsBuilder.UseSqlServer(connectionStr)
.UseLoggerFactory(MyLoggerFactory)
.UseLazyLoadingProxies(); //启用延迟加载

然后EF会为所有可重写的导航属性(必须是virtual关键字)启用延迟加载,如:

#region 导航属性
[ForeignKey("CategoryId")]
public virtual ArticleCategory Category { get; set; }
[ForeignKey("AttachmentId")]
public virtual Attachment Attachment { get; set; }
 
public virtual ICollection<Remark> Remarks { get; set; }
public virtual ICollection<ArticleMusic> ArticleMusics { get; set; }
#endregion

使用:

var article = await context.Article.Where(s => s.Status == 1)
.FirstOrDefaultAsync(s => s.Id == id);
foreach (var item in article.Remarks)
{
Console.WriteLine(item.User.Name);
}

上述代码并未预先加载或者显式加载文章的评论,但是却可以照常获取到文章的评论数据,这就是延迟加载的奥义(在使用时自动加载)。

延迟加载和预先加载对比

预先加载会在第一次查询数据的时候把需要的相关数据一起查询出来(通过一条SQL)。

优点:一次查询,减少数据延迟访问。

缺点:可能会把一些不需要用到的数据查询出来,造成查询效率低。

延迟加载会在使用相关数据的时候再从数据库查询相关数据。

优点:减少不必要的数据读取。

缺点:会多次打开数据库,执行多条SQL,增加数据库负担。

两者并没有严格意义上的优劣,需要根据实际使用场景来进行选择。

版权声明:本文由不落阁原创出品,转载请注明出处!

本文链接:http://www.leo96.com/article/detail/46

原文地址:https://www.cnblogs.com/hanjun0612/p/13723404.html