让我们说我上了这个课:
class Parent(Base):
children = relationship(Child)
class Child(Base):
parent_id = Column(Integer, ForeignKey('parent.id'))
sex = Column(String)
is_married = Column(Boolean)
is_working = Column(Boolean)
我想要一些要计算的只读属性,例如has_single_daughters
或has_working_sons
。我可以这样写:
class Parent(Base):
children = relationship(Child)
def has_working_sons(self):
for child in children:
if child.is_working:
return True
return False
但是,如果我们查询很多父母,每个父母都有几个孩子,那么想象一下性能会受到影响。我想缓存一次计算它的每个属性,就像这样:
class Parent(Base):
children = relationship(Child)
def on_load_or_update_children_collection(self):
for child in self.children:
if child.is_working:
if child.sex == 'M':
self.has_working_sons = True
# ... and so on and so forth
在这种情况下,on_load_children
应该链接到收集事件,但是我没有发现任何有效的方法。我热切地在某些查询中使用joinedload
来加载相关属性。
我可以使用任何事件或其他方法吗?
答案 0 :(得分:1)
我有一个解决方案,我相信它是有效的,并且可以通过使用Child
作为关系的collection_class
来基于set
属性的组合轻松创建只读属性。
您可以使用Alternate Join Condition惯用语来创建作为Parent
子集的子集的关系属性,例如,下面的关系将返回所有女子: / p>
female_children = sa.orm.relationship(
'Child', collection_class=set,
primaryjoin='and_(Parent.id == Child.parent_id, Child.sex == "female")'
)
除了要注意collection_class=set
的规范之外,我将不做过多解释,因为它与上面链接的已记录示例非常相似。 / p>
我继续为Child
(所有collection_class=set
)的每种可能分类创建类似的关系:
class Parent(Base):
id = sa.Column(sa.Integer, primary_key=True)
children = sa.orm.relationship('Child', collection_class=set)
male_children = sa.orm.relationship(
'Child', collection_class=set,
primaryjoin='and_(Parent.id == Child.parent_id, Child.sex == "male")'
)
female_children = sa.orm.relationship(
'Child', collection_class=set,
primaryjoin='and_(Parent.id == Child.parent_id, Child.sex == "female")'
)
working_children = sa.orm.relationship(
'Child', collection_class=set,
primaryjoin='and_(Parent.id == Child.parent_id, Child.is_working == True)'
)
married_children = sa.orm.relationship(
'Child', collection_class=set,
primaryjoin='and_(Parent.id == Child.parent_id, Child.is_married == True)'
)
子模型是您定义的(我正在使用MySQL的字符串长度除外):
class Child(Base):
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.Integer, sa.ForeignKey('parent.id'))
sex = sa.Column(sa.String(6))
is_married = sa.Column(sa.Boolean)
is_working = sa.Column(sa.Boolean)
以下是一些测试数据:
s = Session()
parent = Parent()
s.add(parent)
parent.children.update([
Child(sex='male', is_married=True, is_working=False),
Child(sex='female', is_married=True, is_working=True),
Child(sex='male', is_married=False, is_working=True)
])
s.commit()
查询Parent
时,joinedload
的所有关系都使您只需要一次往返数据库(如您所说的效率问题,尽管这并非绝对必要,延迟加载也可以正常工作):
parent = s.query(Parent).options(sa.orm.joinedload('*')).first()
print(parent.children)
# InstrumentedSet({Child(is_married=False, id=3, is_working=True, sex=male, parent_id=1), Child(is_married=True, id=2, is_working=True, sex=female, parent_id=1), Child(is_married=True, id=1, is_working=False, sex=male, parent_id=1)})
print(parent.female_children)
# InstrumentedSet({Child(is_married=True, id=2, is_working=True, sex=female, parent_id=1)})
print(parent.male_children)
# InstrumentedSet({Child(is_married=False, id=3, is_working=True, sex=male, parent_id=1), Child(is_married=True, id=1, is_working=False, sex=male, parent_id=1)})
print(parent.married_children)
# InstrumentedSet({Child(is_married=True, id=2, is_working=True, sex=female, parent_id=1), Child(is_married=True, id=1, is_working=False, sex=male, parent_id=1)})
由于Parent
上的关系属性均为sets
,因此可以对其进行非常有效的操作。例如,这里是一个正在工作的男孩的隔离:
print(parent.working_children & parent.male_children)
# {Child(is_married=False, id=3, is_working=True, sex=male, parent_id=1)}
然后,您可以在Parent
模型上定义所需的任何只读属性,下面是一些示例:
class Parent(Base):
### excluded all of the columns and relationships for brevity ###
@property
def working_sons(self):
return self.working_children & self.male_children
@property
def working_daughters(self):
return self.working_children & self.female_children
@property
def unmarried_children(self):
return self.children - self.married_children
@property
def married_unemployed_sons(self):
return (
self.male_children - self.working_children & self.married_children)
print(parent.working_sons)
# {Child(is_married=False, id=3, is_working=True, sex=male, parent_id=1)}
print(parent.working_daughters)
# {Child(is_married=True, id=2, is_working=True, sex=female, parent_id=1)}
print(parent.unmarried_children)
# {Child(is_married=False, id=3, is_working=True, sex=male, parent_id=1)}
print(parent.married_unemployed_sons)
# {Child(is_married=True, id=1, is_working=False, sex=male, parent_id=1)}
由于这些都是已设置的操作,因此它们非常有效,但是如果需要,您可以在首次访问时将其结果缓存。
您的问题专门引用了bool
属性has_working_sons
,因此在测试Parent.working_sons
是否有工作儿子时,您可以使用Parent
属性的真实性(例如if parent.working_sons: ...
),或者如果确实需要将其用作bool(Parent.working_sons)
/ True
,则使用False
。