EF Core 6.0 Web API中一对多关联GET请求如何加载嵌套数据?
问题描述
我用EF Power Tools生成了以下实体类和DbContext,底层表通过CategoryId建立一对多关联:
Category.cs
public partial class Category { public Category() { Folders = new HashSet<Folder>(); Reports = new HashSet<Report>(); } public int CategoryId { get; set; } public string CategoryName { get; set; } public string CategoryDescription { get; set; } public string ImagePath { get; set; } public virtual ICollection<Folder> Folders { get; set; } public virtual ICollection<Report> Reports { get; set; } }
Folder.cs
public partial class Folder { public Folder() { Reports = new HashSet<Report>(); } public int FolderId { get; set; } public string FolderName { get; set; } public int CategoryId { get; set; } public string FolderDescription { get; set; } public string FolderImagePath { get; set; } public virtual Category Category { get; set; } public virtual ICollection<Report> Reports { get; set; } }
DbContext配置
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Category>(entity => { entity.HasKey(e => e.CategoryId) .IsClustered(false); entity.ToTable("Category"); entity.Property(e => e.CategoryId) .ValueGeneratedNever() .HasColumnName("CategoryID"); entity.Property(e => e.CategoryDescription) .HasMaxLength(300) .IsUnicode(false); entity.Property(e => e.CategoryName) .HasMaxLength(100) .IsUnicode(false); entity.Property(e => e.ImagePath) .HasMaxLength(250) .IsUnicode(false); } ); modelBuilder.Entity<Folder>(entity => { entity.HasKey(e => e.FolderId) .IsClustered(false); entity.ToTable("Folder"); entity.Property(e => e.FolderId) .ValueGeneratedNever() .HasColumnName("FolderID"); entity.Property(e => e.CategoryId).HasColumnName("CategoryID"); entity.Property(e => e.FolderDescription) .HasMaxLength(300) .IsUnicode(false); entity.Property(e => e.FolderImagePath) .HasMaxLength(250) .IsUnicode(false); entity.Property(e => e.FolderName) .HasMaxLength(100) .IsUnicode(false); entity.HasOne(d => d.Category) .WithMany(p => p.Folders) .HasForeignKey(d => d.CategoryId) .OnDelete(DeleteBehavior.ClientSetNull) .HasConstraintName("FK_Folder_01"); }); OnModelCreatingPartial(modelBuilder); }
我写了如下API接口:
[HttpGet] public async Task<ActionResult<IEnumerable<Category>>> GetCategories() { return await _context.Categories.ToListAsync(); }
返回结果里folders数组是空的:
[ { "categoryId": 1, "categoryName": "Some Name", "folders": [], "reports": [] }, { "categoryId": 2, "categoryName": "Another Name", "folders": [], "reports": [] }, ]
尝试了以下代码也没解决:
public async Task<ActionResult<IEnumerable<Category>>> GetCategories() { var query = (from r in _context.Categories from bu in _context.Folders where r.CategoryId == bu.CategoryId select r ).ToListAsync(); return await query; }
请问如何让返回结果中的folders数组填充对应的关联数据?
解决方案
1. 使用Include显式加载关联数据
Entity Framework Core默认不会自动加载关联的导航属性(除非启用延迟加载),要获取关联的Folders,需要用Include方法指定要加载的导航属性:
基础写法
[HttpGet] public async Task<ActionResult<IEnumerable<Category>>> GetCategories() { // 加载每个Category对应的Folders var categories = await _context.Categories .Include(c => c.Folders) // 显式加载Folders导航属性 .ToListAsync(); return categories; }
如果需要加载多层关联(比如Folders下的Reports)
如果还需要返回每个Folder对应的Reports,可以用ThenInclude:
var categories = await _context.Categories .Include(c => c.Folders) .ThenInclude(f => f.Reports) // 继续加载Folders下的Reports .ToListAsync();
2. 为什么你之前的查询方式无效
你尝试的from r in _context.Categories from bu in _context.Folders这种写法是做了笛卡尔积查询,会返回重复的Category对象(每个Category对应多少Folder就返回多少次),而且并没有加载导航属性,只是筛选出有对应Folder的Category,但EF不会自动填充Folders集合,所以返回的结果里folders还是空的。
3. 关于延迟加载的说明
虽然可以启用延迟加载来自动加载关联数据,但不推荐在Web API场景使用:
- 需要确保导航属性是
virtual(你的实体已经满足),并且在DbContext中启用延迟加载:protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("你的连接字符串") .UseLazyLoadingProxies(); // 启用延迟加载代理 } - 但延迟加载会在序列化实体时触发额外的数据库查询,可能导致N+1问题,而且容易引发循环引用(Category包含Folders,Folder又包含Category),需要额外配置序列化忽略循环引用,所以显式加载(Include)是更可控的方案。
内容的提问来源于stack exchange,提问作者HALO
相关产品推荐
相关产品推荐

