我正在尝试实现面向用户的文章预览列表,即使删除了Article
,也会保持其大小。因此,如果列表中有四个对象[1, 2, 3, 4]
而另一个已删除,我希望它包含[1, 2, None, 4]
。
我使用与secondary
表的关系。目前,删除Article
或PreviewList
将删除该表中的行。我已尝试过级联选项,但它们似乎直接影响相关项目,而不是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
,您如何建立多对多关系?"
答案 0 :(得分:1)
给定的示例似乎假设相关文章的顺序即使在删除时也会保留。有多种方法,例如Ordering List扩展,但更容易首先解决保留与已删除文章的关联的问题。这似乎是association object和proxy的用例。
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')
如果发生这些变化,则不会打印“失败”。