【问题标题】:Exclude soft deleted items in self referential relationship SQLAlchemy在自引用关系 SQLAlchemy 中排除软删除项
【发布时间】:2014-10-31 19:44:54
【问题描述】:

我目前在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 设置为真的孩子。我很确定问题是它正在检查 is_deleted 与父级,但我不知道从这里去哪里。

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

【问题讨论】:

    标签: python sql sqlalchemy


    【解决方案1】:

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

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

    顺便说一句 - 但我想说这个问题是 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
    

    【讨论】:

      【解决方案2】:

      我试图回答这个问题。我的解决方案应该适用于 SQLAlchemy>=0.8。

      实际上这里并没有什么令人惊讶的地方,但是在使用此类模式时必须小心谨慎,因为Sessions 身份映射的状态不会一直反映数据库的状态。

      我在relationship 中使用了post_update 开关来打破由此设置产生的循环依赖性。如需更多信息,请查看SQLAlchemy documentation about this

      警告Session 并不总是反映数据库的状态这一事实可能会导致严重的错误和其他混乱。在这个例子中,我使用expire_all 来显示数据库的真实状态,但这不是一个好的解决方案,因为它会重新加载所有 对象并且所有未flushed 的更改都丢失了。请谨慎使用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()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-09-13
        • 1970-01-01
        • 1970-01-01
        • 2021-09-29
        • 2019-03-06
        • 2018-02-02
        • 2017-04-05
        相关资源
        最近更新 更多