如何用LINQ在Cosmos NoSQL中展平嵌套结构指定属性?
三级嵌套数据结构的LINQ查询实现(替换SQL)
问题描述
我有一个三级嵌套的数据结构,示例访问路径为top.Middles[y].Inners[z].Secret,需要获取所有最内层Secret等于指定值(如'Forbidden')的Outer、Middle、Inner的ID列表。
已实现的SQL方案
符合需求的SQL语句:
SELECT t.id topId, m.id as middleId, b.id as bottomId FROM Tops t join m in t.Middles join b IN m.Bottoms where b.Secret = 'Forbidden'
执行后得到的结果示例:
[ { "topId": "296f9023-f150-45cd-ad38-a7be2ac83f41", "middleId": "101c24bc-71cb-4b6b-a6cf-6979be61be45", "bottomId": "812ac30c-79d4-4c62-94c2-a8cd3eb19bb6" }, { "topId": "296f9023-f150-45cd-ad38-a7be2ac83f41", "middleId": "101c24bc-71cb-4b6b-a6cf-6979be61be45", "bottomId": "9b298db2-db68-4a0c-82f4-d5bd270aa72b" }, { "topId": "296f9023-f150-45cd-ad38-a7be2ac83f41", "middleId": "350f63eb-e41a-493f-9e6d-740818242b7e", "bottomId": "56a51a6c-4e6b-4c52-bbc7-0d71b2486c76" }, { "topId": "1ebdb15f-5864-4e1e-860d-e3a97696da38", "middleId": "ce3bb505-5412-4368-a30e-7f6ce7838a95", "bottomId": "093c7d30-97bb-4fcf-9bd7-0814cbb92571" }, { "topId": "1ebdb15f-5864-4e1e-860d-e3a97696da38", "middleId": "1b239750-1a91-4f0c-9fb1-b1d9e333349e", "bottomId": "b7c27305-59a3-4af9-93ed-5ca272472d37" }, { "topId": "1ebdb15f-5864-4e1e-860d-e3a97696da38", "middleId": "a70b3933-3cde-44d0-80a8-fef6d958ce4a", "bottomId": "30c74577-ef59-41ec-8fd2-0ded3f3c30a2" } ]
需求转换
该SQL实现符合需求,但希望改用LINQ实现以获得强类型支持和编译器属性名校验。目标方法为Example.GetTopsContainingTheSecretWord,调用方式如下:
var topsContainingTheSecretWord = new Example().GetTopsContainingTheSecretWord("Forbidden");
实体类定义
public class Outer { public string id { get; set; } public string Name { get; set; } public List<Middle> Middles { get; set; } } public class Middle { public string id { get; set; } public string Name { get; set; } public List<Inner> Inners { get; set; } } public class Inner { public string id { get; set; } public string Secret { get; set; } public string Name { get; set; } } public record ResultRecord( Guid TopId, Guid MiddleId, Guid BottomId );
修正后的LINQ实现
原代码存在查询类型错误、嵌套集合名称不匹配、上层ID丢失等问题,以下是修正后的可运行代码:
public class Example { private readonly Container _plansContainer; // 通过构造函数注入Cosmos容器实例 public Example(Container plansContainer) { _plansContainer = plansContainer; } public async Task<List<ResultRecord>> GetTopsContainingTheSecretWord( string secretWord, CancellationToken cancellationToken = default ) { var query = _plansContainer.GetItemLinqQueryable<Outer>(true) // 展开Outer的Middles,同时保留Outer的ID .SelectMany(outer => outer.Middles.Select(middle => new { outer, middle })) // 展开Middle的Inners并过滤Secret匹配项,同时构造ResultRecord .SelectMany( om => om.middle.Inners.Where(inner => inner.Secret == secretWord), (om, inner) => new ResultRecord( Guid.Parse(om.outer.id), Guid.Parse(om.middle.id), Guid.Parse(inner.id) ) ) .ToFeedIterator(); var results = new List<ResultRecord>(); while (query.HasMoreResults) { var response = await query.ReadNextAsync(cancellationToken); results.AddRange(response.Resource); } return results; } }
修正说明
- 初始查询目标改为
Outer类型,匹配数据根实体 - 使用带结果选择器的
SelectMany,在展开嵌套集合时保留上层实体ID,避免信息丢失 - 修正集合名称错误:将原代码中的
m.Bottoms改为om.middle.Inners,匹配实体类定义 - 添加字符串ID到Guid的转换,符合
ResultRecord的类型要求 - 完善容器注入逻辑,确保
_plansContainer正确初始化 - 在异步方法中传入取消令牌,符合异步编程最佳实践
内容的提问来源于stack exchange,提问作者DrFloyd5
相关产品推荐
相关产品推荐

