我有两个具有简单一对多关系的 Flask-SQLAlchemy 模型,如下面的最小示例:
class School(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30))
address = db.Column(db.String(30))
class Teacher(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30))
id_school = db.Column(db.Integer, db.ForeignKey(School.id))
school = relationship('School', backref='teachers')
然后我向使用该关系的教师添加一个混合属性,如下所示:
@hybrid_property
def school_name(self):
return self.school.name
当我将它用作
teacher_instance.school_name
时,该属性工作得很好。但是,我也想进行像 Teacher.query.filter(Teacher.school_name == 'x')
这样的查询,但这给了我一个错误:
`AttributeError: Neither 'InstrumentedAttribute' object nor
'Comparator' object has an attribute 'school_name'`.
按照 SQLAlchemy 文档,我添加了一个简单的混合表达式,如下所示:
@school_name.expression
def school_name(cls):
return School.name
但是,当我再次尝试相同的查询时,它会生成一个不带 join 子句的 SQL 查询,因此我获得了 School 中的所有可用行,而不仅仅是那些与 Teacher 中的外键匹配的行。
从 SQLAlchemy 文档中我意识到表达式需要一个已经存在连接的上下文,所以我再次尝试查询:
Teacher.query.join(School).filter(Teacher.school_name == 'x')
这确实有效,但如果我需要了解 School 模型的知识来获得语法糖,它就违背了试图将语法糖放在那里的目的。我希望有一种方法可以在表达式中加入该连接,但我在任何地方都找不到它。该文档有一个示例,其中表达式返回直接使用
select()
构建的子查询,但即使这样对我来说也不起作用。
有什么想法吗?
更新
在 Eevee 下面的回答之后,我按照建议使用了关联代理并且它有效,但我也对它应该与
select()
子查询一起使用的评论感到好奇,并试图找出我做错了什么。我最初的尝试是:
@school_name.expression
def school_name(cls):
return select(School.name).where(cls.id_school == School.id).as_scalar()
事实证明这给了我一个错误,因为我错过了 select() 中的列表。下面的代码工作正常:
@school_name.expression
def school_name(cls):
return select([School.name]).where(cls.id_school == School.id).as_scalar()
对于像这样的简单情况,一种更简单的方法是关联代理:
class Teacher(db.Model):
school_name = associationproxy('school', 'name')
这支持自动查询(至少使用
==
)。
我很好奇混合
select()
示例为何不适合您,因为这是在混合中解决此问题的最简单方法。为了完整起见,您还可以使用transformer直接修改查询而不是子查询。
有人可以分享如何修改解决方案,如果混合属性是这样的:
@hybrid_property
def school_name(self):
return self.school.name + " - " + str(self.id_school)
在这种情况下,它不仅仅是一个简单的关联,而是一个计算。我实际上在我的实际代码中进行了数值计算。