Entity Framework Core中文件关联带嵌套结构标签及标签值的查询输出格式优化问题
我太懂你这种感受了——明明数据都存在库里,关联关系也没毛病,但查出来的JSON结构就是和预期的差一大截,标签和对应的选中值拆成了两个独立数组,看着特别别扭,完全不符合前端或者业务逻辑想要的嵌套结构对吧?
针对你现在的情况,有几个实用的解决方案,我一个个给你捋清楚:
方案一:查询后在内存中重组数据
先把所有关联数据一次性查出来,然后用Linq在内存里把结构重新拼成你想要的样子,这种方式改动最小,适合快速解决问题:
首先把需要的关联数据都Include完整,避免后续延迟加载踩坑:
var files = await databaseContext.Files .Include(f => f.Tags) .ThenInclude(ft => ft.Tag) .ThenInclude(t => t.TagValueCategories) .ThenInclude(tvc => tvc.TagValues) .Include(f => f.TagValues) .ThenInclude(fv => fv.TagValue) .ThenInclude(tv => tv.TagValueCategory) .ToListAsync();
接着就可以在内存里重组结构,把选中的TagValue匹配到对应的Tag和分类下面:
var formattedFiles = files.Select(file => new { file.Id, file.Name, Tags = file.Tags.Select(fileTag => { var targetTag = fileTag.Tag; // 找出当前文件关联的、属于这个标签的所有选中值 var selectedValues = file.TagValues .Where(fileTagValue => fileTagValue.TagValue.TagValueCategory.TagId == targetTag.Id) .Select(fv => fv.TagValue) .ToList(); // 把选中值填充到对应的分类里 var categoriesWithSelectedValues = targetTag.TagValueCategories.Select(category => new { category.Id, category.Name, TagValues = category.TagValues.Where(value => selectedValues.Contains(value)).ToList() }).ToList(); return new { targetTag.Id, targetTag.Name, TagValueCategories = categoriesWithSelectedValues }; }).ToList() }).ToList();
这样得到的formattedFiles就是你想要的嵌套结构了。
方案二:直接用DTO投影查询
如果数据量比较大,不想在内存里做太多处理,也可以直接在EF查询阶段就投影成目标结构,让EF帮你生成高效的SQL,同时一步到位得到想要的结果:
首先定义和你期望输出对应的DTO类:
public class FileDto { public int Id { get; set; } public string Name { get; set; } public List<TagDto> Tags { get; set; } = new List<TagDto>(); } public class TagDto { public int Id { get; set; } public string Name { get; set; } public List<TagValueCategoryDto> TagValueCategories { get; set; } = new List<TagValueCategoryDto>(); } public class TagValueCategoryDto { public int Id { get; set; } public string Name { get; set; } public List<TagValueDto> TagValues { get; set; } = new List<TagValueDto>(); } public class TagValueDto { public int Id { get; set; } public string Name { get; set; } }
然后用EF的投影查询直接构造DTO:
var formattedFiles = await databaseContext.Files .Select(file => new FileDto { Id = file.Id, Name = file.Name, Tags = file.Tags.Select(fileTag => new TagDto { Id = fileTag.Tag.Id, Name = fileTag.Tag.Name, TagValueCategories = fileTag.Tag.TagValueCategories.Select(category => new TagValueCategoryDto { Id = category.Id, Name = category.Name, TagValues = category.TagValues .Where(value => file.TagValues.Any(fv => fv.TagValueId == value.Id)) .Select(value => new TagValueDto { Id = value.Id, Name = value.Name }).ToList() }).ToList() }).ToList() }) .ToListAsync();
这种方式不需要后续的内存处理,查询出来直接就是符合要求的结构,性能也更优。
方案三:调整实体关联结构(可选)
如果你的业务场景允许,也可以考虑调整实体之间的关联方式,比如让File和Tag直接建立多对多关系,同时在Tag里增加一个导航属性表示当前文件选中的TagValue,但这种方式需要修改数据库结构,改动相对较大,如果你只是想解决输出格式问题,前两种方案会更轻量。
最后提醒一下:用第一种方案的时候,一定要确保所有需要的关联数据都通过Include加载完成,不然容易触发EF的延迟加载,导致N+1查询的性能问题;用投影查询的话就不用担心这个问题,EF会自动处理关联逻辑。
备注:内容来源于stack exchange,提问作者Saeverix

