SQLAlchemy:对象列表,如果它们被删除则保留引用

时间:2018-04-03 02:09:09

标签: python sqlalchemy

我正在尝试实现面向用户的文章预览列表,即使删除了Article,也会保持其大小。因此,如果列表中有四个对象[1, 2, 3, 4]而另一个已删除,我希望它包含[1, 2, None, 4]

我使用与secondary表的关系。目前,删除ArticlePreviewList将删除该表中的行。我已尝试过级联选项,但它们似乎直接影响相关项目,而不是secondary表的内容。

下面的代码段会测试所需的行为:删除Article应保留ArticlePreviewListAssociation中的行,但删除PreviewList应删除它(而不是Article )。

在下面的代码中,删除Article会保留ArticlePreviewListAssociation,但pl.articles不会将其视为列表条目。

from db import DbSession, Base, init_db
from sqlalchemy import Column, String, Integer, ForeignKey
from sqlalchemy.orm import relationship

session = DbSession()


class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer, primary_key=True)
    title = Column(String)


class PreviewList(Base):
    __tablename__ = 'preview_lists'
    id = Column(Integer, primary_key=True)
    articles = relationship('Article', secondary='associations')


class ArticlePreviewListAssociation(Base):
    __tablename__ = 'associations'
    article_id = Column(Integer, ForeignKey('articles.id'), nullable=True)
    previewlist_id = Column(Integer, ForeignKey('preview_lists.id'), primary_key=True)

    article = relationship('Article')
    preview_list = relationship('PreviewList')


init_db()

print(f"Creating test data")
a = Article(title="StackOverflow: 'Foo' not setting 'Bar'?")
pl = PreviewList(articles=[a])
session.add(a)
session.add(pl)
session.commit()

print(f"ArticlePreviewListAssociations: {session.query(ArticlePreviewListAssociation).all()}")

print(f"Deleting PreviewList")
session.delete(pl)
associations = session.query(ArticlePreviewListAssociation).all()
print(f"ArticlePreviewListAssociations: should be empty: {associations}")
if len(associations) > 0:
    print("FAIL")

print("Reverting transaction")
session.rollback()

print("Deleting article")
session.delete(a)
articles_in_list = pl.articles
associations = session.query(ArticlePreviewListAssociation).all()
print(f"ArticlePreviewListAssociations: should not be empty: {associations}")
if len(associations) == 0:
    print("FAIL")
print(f"Articles in PreviewList: should not be empty: {articles_in_list}")
if len(articles_in_list) == 0:
    print("FAIL")
# desired outcome: pl.articles should be [None], not []

print("Reverting transaction")
session.rollback()

这可能归结为"如果pk_A == 1 and pk_B == NULL在A列表中包含None,您如何建立多对多关系?"

1 个答案:

答案 0 :(得分:1)

给定的示例似乎假设相关文章的顺序即使在删除时也会保留。有多种方法,例如Ordering List扩展,但更容易首先解决保留与已删除文章的关联的问题。这似乎是association objectproxy的用例。

Article类获得新关系,以便在会话中级联删除。默认的ORM-level cascading行为是将外键设置为NULL,但如果未加载相关的关联对象,我们希望让DB执行此操作,因此使用passive_deletes=True

class Article(Base):
    __tablename__ = 'articles'

    id = Column(Integer, primary_key=True)
    title = Column(String)

    previewlist_associations = relationship(
        'ArticlePreviewListAssociation', back_populates='article',
        passive_deletes=True)

而不是多对多关系PreviewList使用关联对象模式,以及替换多对多关系的关联代理。这次级联有点不同,因为如果删除了父PreviewList,则应删除关联对象:

class PreviewList(Base):
    __tablename__ = 'preview_lists'

    id = Column(Integer, primary_key=True)

    article_associations = relationship(
        'ArticlePreviewListAssociation', back_populates='preview_list',
        cascade='all, delete-orphan', passive_deletes=True)

    articles = association_proxy(
        'article_associations', 'article',
        creator=lambda a: ArticlePreviewListAssociation(article=a))

最初,关联对象使用previewlist_id作为主键,但PreviewList只能包含一个Article。代理键解决了这个问题。外键配置包括DB级级联。这些是使用被动删除的原因:

class ArticlePreviewListAssociation(Base):
    __tablename__ = 'associations'

    id = Column(Integer, primary_key=True)
    article_id = Column(
        Integer, ForeignKey('articles.id', ondelete='SET NULL'))
    previewlist_id = Column(
        Integer, ForeignKey('preview_lists.id', ondelete='CASCADE'),
        nullable=False)

    # Using a unique constraint on a nullable column is a bit ugly, but
    # at least this prevents inserting an Article multiple times to a
    # PreviewList.
    __table_args__ = (UniqueConstraint(article_id, previewlist_id), )

    article = relationship(
        'Article', back_populates='previewlist_associations')
    preview_list = relationship(
        'PreviewList', back_populates='article_associations')

如果发生这些变化,则不会打印“失败”。