无法执行SelectMany,如何让usersNodes保持IQueryable不加载到内存?
解决Cannot perform SelectMany - Unable to materialize collection问题,保持IQueryable结果
问题根源
报错的核心原因是HorusParentPathList是标记了[NotMapped]的属性,它的集合逻辑是在内存中将数据库存储的HorusParentPath字符串拆分成Guid列表。EF Core无法将内存集合的SelectMany操作转换为数据库可执行的SQL,因此抛出无法物化集合的错误。
解决方案:直接在数据库端处理字符串拆分
既然HorusParentPathList完全依赖数据库中的HorusParentPath字段生成,我们可以跳过这个内存属性,直接在LINQ查询中对HorusParentPath做拆分操作,让EF Core能将整个逻辑转换为SQL执行,从而保持查询的IQueryable类型。
方法1:使用EF Core内置字符串拆分函数(EF Core 5+)
如果你的EF Core版本是5.0及以上,可直接利用数据库内置的拆分函数(以SQL Server为例,使用EF.Functions.SplitString):
var nodes = from n in _db.TreeNodes.Where(n => passwordCycles.Select(c => c.HorusId).Contains(n.HorusId)) from path in _db.TreeNodes.Where(p => EF.Functions.Like(p.Path, n.Path + "%") && p.Type == TreeNodeType.User) select new { path.HorusId, path.HorusParentPath }; var usersNodes = nodes .SelectMany(e => EF.Functions.SplitString(e.HorusParentPath, "'") .Where(s => !string.IsNullOrEmpty(s)) .Select(s => new { HorusParentRow = Guid.Parse(s), HorusId = e.HorusId })) .Distinct();
方法2:自定义数据库函数映射(兼容低版本EF Core)
如果EF Core版本较低,或数据库不支持内置拆分函数,可自定义数据库函数并映射到EF中:
- 在数据库中创建拆分函数(SQL Server示例):
CREATE FUNCTION dbo.SplitHorusParentPath(@path NVARCHAR(450)) RETURNS TABLE AS RETURN ( SELECT TRY_CAST(value AS UNIQUEIDENTIFIER) AS HorusGuid FROM STRING_SPLIT(@path, '''') WHERE value IS NOT NULL AND value <> '' )
- 在DbContext中映射该函数:
[DbFunction("SplitHorusParentPath", Schema = "dbo")] public IQueryable<Guid> SplitHorusParentPath(string path) { var pathParam = new SqlParameter("@path", path); return Set<Guid>().FromSqlRaw("SELECT HorusGuid FROM dbo.SplitHorusParentPath(@path)", pathParam); }
- 在查询中使用自定义函数:
var nodes = from n in _db.TreeNodes.Where(n => passwordCycles.Select(c => c.HorusId).Contains(n.HorusId)) from path in _db.TreeNodes.Where(p => EF.Functions.Like(p.Path, n.Path + "%") && p.Type == TreeNodeType.User) select new { path.HorusId, path.HorusParentPath }; var usersNodes = nodes .SelectMany(e => _db.SplitHorusParentPath(e.HorusParentPath) .Select(g => new { HorusParentRow = g, HorusId = e.HorusId })) .Distinct();
核心逻辑说明
两种方案都是将字符串拆分的逻辑转移到数据库端执行,避免依赖内存中的HorusParentPathList属性。这样整个查询的所有操作都能被EF Core转换为SQL,usersNodes将保持IQueryable类型,不会提前加载数据到内存。
内容的提问来源于stack exchange,提问作者guiz
相关产品推荐
相关产品推荐

