延迟加载是ORM框架中常见的数据加载策略,指当访问实体的导航属性时,才自动从数据库加载关联数据,而不是在初始查询时就加载所有关联数据。在EF Core中,延迟加载可以有效减少初始查询的数据量,提升查询效率,尤其适合关联数据较多、不需要每次都加载全部关联数据的场景。

延迟加载的开启方式
方式一:使用代理(Proxy)实现延迟加载
这种方式需要安装Microsoft.EntityFrameworkCore.Proxies包,并且在实体类的导航属性上标记为virtual,EF Core会在运行时生成代理类,当访问导航属性时自动触发关联数据查询。
首先需要在DbContext配置中启用延迟加载代理:
// 安装包:Microsoft.EntityFrameworkCore.Proxies
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// 启用延迟加载代理
optionsBuilder.UseLazyLoadingProxies()
.UseSqlServer("Server=.;Database=TestDb;Trusted_Connection=True;");
}
}
实体类的定义需要导航属性为virtual:
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
// 导航属性标记为virtual,支持代理延迟加载
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
// 导航属性标记为virtual
public virtual Blog Blog { get; set; }
}
使用时的代码示例:
using var context = new AppDbContext(); // 初始查询只加载Blog本身的数据,不会加载关联的Posts var blog = context.Blogs.FirstOrDefault(b => b.Id == 1); // 访问Posts属性时,EF Core会自动执行查询加载关联的Post数据 var postCount = blog.Posts.Count;
方式二:使用ILazyLoader注入实现延迟加载
这种方式不需要导航属性为virtual,也不需要生成代理类,通过在实体中注入ILazyLoader服务,手动控制关联数据的加载逻辑。
首先需要安装Microsoft.EntityFrameworkCore.Abstractions包,实体类定义如下:
using Microsoft.EntityFrameworkCore.Infrastructure;
public class Blog
{
private readonly ILazyLoader _lazyLoader;
private ICollection<Post> _posts;
public Blog()
{
}
public Blog(ILazyLoader lazyLoader)
{
_lazyLoader = lazyLoader;
}
public int Id { get; set; }
public string Title { get; set; }
public ICollection<Post> Posts
{
get => _lazyLoader?.Load(this, ref _posts) ?? _posts;
set => _posts = value;
}
}
public class Post
{
private readonly ILazyLoader _lazyLoader;
private Blog _blog;
public Post()
{
}
public Post(ILazyLoader lazyLoader)
{
_lazyLoader = lazyLoader;
}
public int Id { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog
{
get => _lazyLoader?.Load(this, ref _blog) ?? _blog;
set => _blog = value;
}
}
DbContext不需要额外启用代理配置,使用方式和代理方式类似:
using var context = new AppDbContext(); var blog = context.Blogs.FirstOrDefault(b => b.Id == 1); // 访问Posts时触发延迟加载 var firstPost = blog.Posts.FirstOrDefault();
延迟加载的注意事项
- 延迟加载可能导致N+1查询问题:如果遍历多个实体并访问每个实体的导航属性,会触发多次数据库查询,反而降低性能。这种场景建议使用显式加载或者预先加载(Include)。
- 延迟加载需要在DbContext生命周期内使用:如果DbContext已经被释放,再访问导航属性会抛出异常。
- 代理方式要求导航属性为virtual,并且实体类不能被密封(sealed),否则无法生成代理类。
- 不是所有场景都适合使用延迟加载,对于确定需要加载的关联数据,使用
Include预先加载效率更高。
延迟加载与显式加载、预先加载的对比
| 加载方式 | 触发时机 | 适用场景 |
|---|---|---|
| 延迟加载 | 访问导航属性时自动触发 | 关联数据使用频率低,不确定是否需要加载的场景 |
| 预先加载(Include) | 初始查询时通过Include指定加载 | 确定需要加载关联数据,避免多次查询的场景 |
| 显式加载(Load) | 手动调用Load方法触发 | 初始查询后,根据条件决定是否加载关联数据的场景 |
延迟加载是EF Core提供的便捷特性,但需要根据实际业务场景合理选择,避免滥用导致性能问题。在性能敏感的场景中,建议优先评估预先加载或显式加载是否更合适。