1

在这里定义一些基本结构:

class A( Base ):

    __tablename__ = 'a'
    id            = Column( types.Integer(), primary_key = True )

    abs           = orm.relation( 'AB', lazy='joined' )

class AB( Base ):

    __tablename__ = 'ab'
    id            = Column( types.Integer(), primary_key = True )
    a_id          = Column( types.Integer(), ForeignKey( 'a.id' ) )
    b_id          = Column( types.Integer(), ForeignKey( 'b.id' ) )

    a             = orm.relation( 'A' )
    b             = orm.relation( 'B', lazy='joined' )

class B( Base ):

    __tablename__ = 'b'
    id            = Column( types.Integer(), primary_key = True )

    bas           = orm.relation( 'AB' )

现在说我有一个A与它相关的多个Bs(比如A.id = 1),我想根据这些Bs 进行过滤。我执行以下查询:

a = db.session.query( A ).join( A.abs ).filter( AB.b_id = 1, A.id = 1 ).first()

在这一点上,我希望len( a.abs ) == 1,但事实并非如此。换句话说,应用于连接的过滤器不会传播到 orm.relation。我如何得到这种行为?

4

1 回答 1

1

这个问题的原因在The Zen of Eager Loading中有描述,归结为:您的查询中有两个不同的连接。一种是用于构建正确的连接以进行过滤(这是您使用 生成的.join(A.abs)),另一种是用于加载关系(ORM 基于 自动插入lazy="joined",否则它会在访问时查询它)。

现在有几种方法可以解决这个问题。但首先你应该想想你真正想要什么。因为当你说A.abs你真的说“属于这个A的所有AB条目”时。但是当你指定一个b_id比这不是你想要的,因为这不是这种关系所代表的。所以这是干净的方法:

db.session.query(A, AB).join(A.abs).filter(AB.b_id = 1, A.id = 1)

现在您AB从查询中获得第二个返回的对象。这是如何正确地做到这一点,因为只有一个人AB实际上A.abs会欺骗 ORM:这不是真的(它可能会破坏东西)。但是,如果您坚持这样做,这是可能的。您可以使用sqlalchemy.orm.contains_eager禁用双连接:

db.session.query(A).join(A.abs).options(contains_eager(A.abs)).filter(AB.b_id = 1, A.id = 1)

这将产生一个A.abs只有一个条目具有b_id = 1. 但是,正如已经说过的那样,这不是一个好的解决方案,也不是您应该做的。

作为一个额外的提示,我建议您打开echo=True甚至echo="debug"在您的引擎中,以确保您看到正在执行的查询。如果您查看原始查询,则会在同一个表上看到两个连接。

于 2013-09-18T17:03:37.857 回答