排除自引用关系SQLAlchemy中的软删除项



我目前在Foo:上有一个自引用关系

parent_id = DB.Column(DB.Integer, DB.ForeignKey('foo.id'))
parent = DB.relation(
    'Foo', 
    remote_side=[id], 
    backref=DB.backref(
        'children', 
        primaryjoin=('and_(foo.c.id==foo.c.parent_id, foo.c.is_deleted==False)')
    )
)

现在,我正在尝试排除is_deleted设置为true的任何子项。我很确定问题是它正在对照父对象检查is_deleted,但我不知道从这里开始该怎么办。

如何修改关系,使is_deleted的子级不包括在结果集中?

我试着回答了这个问题。我的解决方案应该使用SQLAlchemy>=0.8。

实际上,这里并没有发生什么令人惊讶的事情,但在使用这种模式时必须格外小心,因为Session的身份映射的状态不会一直反映DB的状态。

我在relationship中使用了post_update开关来打破这种设置产生的循环依赖性。有关更多信息,请参阅SQLAlchemy文档。

警告Session并不总是反映数据库的状态,这可能是导致严重错误和其他混乱的原因。在本例中,我使用expire_all来显示DB的真实状态,但这不是一个好的解决方案,因为它重新加载所有对象,并且所有未使用flush的更改都将丢失。小心使用expireexpire_all

首先我们定义模型

#!/usr/bin/env python
import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy.ext.declarative import declarative_base
engine = sa.create_engine('sqlite:///blah.db')
Base = declarative_base()
Base.bind = engine
class Obj(Base):
    __table__ = sa.Table(
        'objs', Base.metadata,
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('parent_id', sa.Integer, sa.ForeignKey('objs.id')),
        sa.Column('deleted', sa.Boolean),
    )
    # I used the remote() annotation function to make the whole thing more
    # explicit and readable.
    children = orm.relationship(
        'Obj',
        primaryjoin=sa.and_(
            orm.remote(__table__.c.parent_id) == __table__.c.id,
            orm.remote(__table__.c.deleted) == False,
        ),
        backref=orm.backref('parent',
                            remote_side=[__table__.c.id]),
        # This breaks the cyclical dependency which arises from my setup.
        # For more information see: http://stackoverflow.com/a/18284518/15274
        post_update=True,
    )
    def __repr__(self):
        return "<Obj id=%d children=%d>" % (self.id, len(self.children))

然后我们试试

def main():
    session = orm.sessionmaker(bind=engine)
    db = session()
    Base.metadata.create_all(engine)
    p1 = Obj()
    db.add(p1)
    db.flush()
    p2 = Obj()
    p2.deleted = True
    p1.children.append(p2)
    db.flush()
    # prints <Obj id=1 children=1>
    # This means the object is in the `children` collection, even though
    # it is deleted. If you want to prevent this you may want to use
    # custom collection classes (not for novices!).
    print p1
    # We let SQLalchemy forget everything and fetch the state from the DB.
    db.expire_all()
    p3 = db.query(Obj).first()
    # prints <Obj id=1 children=0>
    # This indicates that the children which is still linked is not
    # loaded into the relationship, which is what we wanted.
    print p3
    db.rollback()

if __name__ == '__main__':
    main()

您可能应该在控制器中进行筛选,而不是在模型中。

这不是一个完美的答案:-)

顺便说一句,但我想说这个问题是一个完美的例子,ORM-s和SQL上的抽象层很糟糕。

看起来SQLAlchemy阻碍了程序员,而不是帮助他。

在SQL中,这非常简单。

SELECT parent.*, child.* 
FROM foo AS parent
JOIN foo AS child ON child.parent_id = parent.id
WHERE NOT child.is_deleted

最新更新