
在 SQLAlchemy 异步会话中,当需同时指定具体列(如 ModelA.id, ModelA.name)和预加载关系(如 ModelA.bs)时,直接使用 select(*columns) 会导致 ArgumentError: Query has only expression-based entities 错误;正确解法是基于完整模型构造查询,再通过 with_only_columns() 精确裁剪返回字段。
在 sqlalchemy 异步会话中,当需同时指定具体列(如 `modela.id`, `modela.name`)和预加载关系(如 `modela.bs`)时,直接使用 `select(*columns)` 会导致 `argumenterror: query has only expression-based entities` 错误;正确解法是基于完整模型构造查询,再通过 `with_only_columns()` 精确裁剪返回字段。
该错误的根本原因在于:select(*columns) 生成的是纯表达式(expression-only)查询,不绑定到任何 ORM 实体(如 ModelA),因此无法应用 selectinload()、joinedload() 等针对 ORM 映射器(Mapper)的加载选项——这些选项必须作用于包含实体类的查询(例如 select(ModelA))。
✅ 正确做法是:*始终以 select(ModelA) 为起点,再用 `.with_only_columns(columns)限定返回字段,最后通过.options(...)` 添加关系加载策略**。这样既保留了 ORM 实体上下文,又实现了字段级可控输出。
以下是修复后的异步查询示例:
from sqlalchemy import select
from sqlalchemy.orm import selectinload
async def get(self, fields):
columns, relationships = self.parse_fields(fields, ModelA)
# ✅ 关键:从完整模型开始构建查询
stmt = select(ModelA).with_only_columns(*columns)
# ✅ 可安全添加关系预加载(因 query 仍基于 ModelA 实体)
if relationships:
stmt = stmt.options(*[selectinload(rel) for rel in relationships])
result = await self.session.execute(stmt)
return result.all()
⚠️ 注意事项:
- with_only_columns() 必须在 .options() 之前调用,否则加载选项可能被忽略;
- 传入 selectinload() 的必须是 InstrumentedAttribute(如 ModelA.bs),而非字符串;
- 若 columns 中包含关系属性(如 ModelA.bs),需注意:with_only_columns() 不支持直接传入关系列(会报错),应仅传入标量列(Column 或 Mapped 属性),关系数据由 selectinload 在结果对象上按需填充;
- 若需返回扁平化结构(如 id, name, bs_name),建议改用 join() + add_columns(),而非混合 with_only_columns 与关系加载。
? 总结:SQLAlchemy 的 ORM 加载机制与查询实体强绑定。放弃“纯列查询”的惯性思维,拥抱 select(ModelA).with_only_columns(...).options(...) 模式,即可在异步场景下优雅实现字段与关系的双重动态控制。










