EF Core 8 + PostgreSQL下递归CTE的EXISTS布尔查询报错:ParentFolderId列不存在(TPT结构)
这个问题的核心原因是你混淆了EF Core实体继承(TPT模式)的逻辑和数据库实际的表结构:
在TPT(Table-Per-Type)映射中,父类DriveItem的属性(比如ParentFolderId、IsDeleted)会被存储在独立的DriveItems父表中,子类Folder仅存储自身独有的属性到Folders子表,两个表通过DriveItemId关联。你当前的SQL错误地从Folders表引用了属于父表的ParentFolderId和IsDeleted字段,所以PostgreSQL提示列不存在——因为Folders表确实没有这两个字段!
修正后的解决方案
我们需要调整SQL,通过关联DriveItems表来获取继承属性,同时保持递归逻辑正确:
public async Task<Result<bool>> HasDeletedAncestorAsync(Guid childFolderId) { const string sql = @" WITH RECURSIVE path AS ( -- 初始层:获取目标文件夹的父ID和删除状态(关联父表DriveItems) SELECT f.""DriveItemId"", di.""ParentFolderId"", di.""IsDeleted"" FROM ""Folders"" f JOIN ""DriveItems"" di ON f.""DriveItemId"" = di.""DriveItemId"" WHERE f.""DriveItemId"" = @p0 UNION ALL -- 递归层:向上遍历父文件夹,同样关联父表获取属性 SELECT f.""DriveItemId"", di.""ParentFolderId"", di.""IsDeleted"" FROM ""Folders"" f JOIN ""DriveItems"" di ON f.""DriveItemId"" = di.""DriveItemId"" JOIN path p ON di.""DriveItemId"" = p.""ParentFolderId"" WHERE p.""ParentFolderId"" IS NOT NULL ) SELECT EXISTS ( SELECT 1 FROM path WHERE ""IsDeleted"" = TRUE ); "; bool hasDeleted = await DbContext.Database .SqlQueryRaw<bool>(sql, childFolderId) .SingleAsync(); return Result.Success(hasDeleted); }
关键修正点说明
关联父表获取继承属性:
所有属于DriveItem父类的属性(ParentFolderId、IsDeleted)必须从DriveItems表查询,而不是Folders子表。我们通过DriveItemId将两个表关联,获取完整的文件夹属性。递归逻辑保持正确:
递归层中,我们通过当前路径的ParentFolderId(即父文件夹的DriveItemId)找到对应的文件夹记录,再关联父表获取其上级ID和删除状态,直到遍历到根目录(ParentFolderId为NULL)。EF Core 8的SqlQueryRaw用法:
你的SqlQueryRaw调用本身是正确的(EF Core 8支持直接返回标量类型),问题完全出在SQL的表关联错误上。
为什么你会误解?
在EF Core的实体类中,Folder继承自DriveItem,所以你可以直接通过Folder实体访问ParentFolderId和IsDeleted属性——但这是EF Core提供的抽象,实际数据库中这些属性并不在Folders表中,而是存储在父表DriveItems里。
内容来源于stack exchange

