Подтвердить что ты не робот

Исключить мягкие удаленные элементы в самореляционные отношения 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 не были включены в результирующий набор?

4b9b3361

Ответ 1

Я принял удар, чтобы ответить на это. Мое решение должно работать с SQLAlchemy >= 0.8.

По сути, здесь ничего удивительного не происходит, однако при использовании таких шаблонов следует применять надлежащую осторожность, так как состояние идентификационной карты Session не будет постоянно отражать состояние БД.

Я использовал переключатель post_update в relationship, чтобы разорвать циклическую зависимость, которая возникает из этой настройки. Для получения дополнительной информации см. Документацию SQLAlchemy об этом.

Предупреждение. Тот факт, что Session не всегда отражает состояние БД, может быть причиной неприятных ошибок и других путаниц. В этом примере я использую expire_all, чтобы показать реальное состояние БД, но это не очень хорошее решение, потому что оно перезагружает все объекты, а все un- flush ed изменения теряются. Используйте expire и expire_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()

Ответ 2

Вероятно, вы должны фильтровать в контроллере, а не в модели.

Это не идеальный ответ:-)

BTW - но я хочу сказать, что этот вопрос является прекрасным примером того, что ORM и абстракции слоев над SQL suck.

Похоже, что SQLAlchemy оказывается на пути программиста, а не помогает ему.

В SQL это мертво просто.

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