特殊关系持续模式
指向自身/相互依赖的行的行
这是一种非常具体的情况,其中relationship()必须执行插入和第二次更新才能正确填充行(反之亦然,更新和删除才能在不违反外键约束的情况下进行删除)。这两个用例是:
一个表包含一个指向它自己的外键,并且一行将有一个指向它自己的主键的外键值。
两个表各自包含一个引用另一个表的外键,每个表中的一行引用另一个表。
例如::
user --------------------------------- user_id name related_user_id 1 'ed' 1
或:
widget entry ------------------------------------------- --------------------------------- widget_id name favorite_entry_id entry_id name widget_id 1 'somewidget' 5 5 'someentry' 1
在第一种情况下,行指向自身。从技术上讲,使用PostgreSQL或Oracle等序列的数据库可以使用以前生成的值立即插入行,但依赖自动增量样式主键标识符的数据库则不能。这个 relationship()
在刷新期间始终假定行填充的“父/子”模型,因此除非直接填充主键/外键列, relationship()
需要使用两个语句。
在第二种情况下,必须在任何引用的“entry”行之前插入“widget”行,但在生成“entry”行之前,不能设置“widget”行的“favorite”列。在这种情况下,通常不可能只使用两条INSERT语句插入“widget”和“entry”行;必须执行更新才能满足外键约束。例外情况是,如果将外键配置为“延迟到提交”(某些数据库支持的功能),以及手动填充标识符(再次基本上绕过 relationship()
)
为了能够使用补充更新语句,我们使用 relationship.post_update
选择权 relationship()
. 这指定在插入两行后,应使用UPDATE语句创建两行之间的链接;它还将导致在发出删除之前通过UPDATE取消行之间的关联。旗子应该放在 one 最好是多对一的关系。下面我们将展示一个完整的示例,其中包括两个 ForeignKey
结构:
from sqlalchemy import Integer, ForeignKey, Column from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship Base = declarative_base() class Entry(Base): __tablename__ = 'entry' entry_id = Column(Integer, primary_key=True) widget_id = Column(Integer, ForeignKey('widget.widget_id')) name = Column(String(50)) class Widget(Base): __tablename__ = 'widget' widget_id = Column(Integer, primary_key=True) favorite_entry_id = Column(Integer, ForeignKey('entry.entry_id', name="fk_favorite_entry")) name = Column(String(50)) entries = relationship(Entry, primaryjoin= widget_id==Entry.widget_id) favorite_entry = relationship(Entry, primaryjoin= favorite_entry_id==Entry.entry_id, post_update=True)
当刷新与上述配置相对应的结构时,“widget”行将被插入减去“favorite”(最爱)条目“id”值,然后所有“entry”(条目)行将被插入引用父“widget”(小部件)行,然后更新语句将填充“widget”(小部件)表的“favorite”(最爱)条目“id”(每次一行)列(此时g):
>>> w1 = Widget(name='somewidget') >>> e1 = Entry(name='someentry') >>> w1.favorite_entry = e1 >>> w1.entries = [e1] >>> session.add_all([w1, e1]) sql>>> session.commit() BEGIN (implicit) INSERT INTO widget (favorite_entry_id, name) VALUES (?, ?) (None, 'somewidget') INSERT INTO entry (widget_id, name) VALUES (?, ?) (1, 'someentry') UPDATE widget SET favorite_entry_id=? WHERE widget.widget_id = ? (1, 1) COMMIT
我们可以指定的另一个配置是在 Widget
以确保 favorite_entry_id
指的是 Entry
也指这个 Widget
. 我们可以使用复合外键,如下图所示:
from sqlalchemy import Integer, ForeignKey, String, \ Column, UniqueConstraint, ForeignKeyConstraint from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship Base = declarative_base() class Entry(Base): __tablename__ = 'entry' entry_id = Column(Integer, primary_key=True) widget_id = Column(Integer, ForeignKey('widget.widget_id')) name = Column(String(50)) __table_args__ = ( UniqueConstraint("entry_id", "widget_id"), ) class Widget(Base): __tablename__ = 'widget' widget_id = Column(Integer, autoincrement='ignore_fk', primary_key=True) favorite_entry_id = Column(Integer) name = Column(String(50)) __table_args__ = ( ForeignKeyConstraint( ["widget_id", "favorite_entry_id"], ["entry.widget_id", "entry.entry_id"], name="fk_favorite_entry" ), ) entries = relationship(Entry, primaryjoin= widget_id==Entry.widget_id, foreign_keys=Entry.widget_id) favorite_entry = relationship(Entry, primaryjoin= favorite_entry_id==Entry.entry_id, foreign_keys=favorite_entry_id, post_update=True)
上面的映射具有一个组合 ForeignKeyConstraint
桥接 widget_id
和 favorite_entry_id
柱。确保 Widget.widget_id
仍然是我们指定的“自动增量”列 Column.autoincrement
价值观 "ignore_fk"
在 Column
以及每个 relationship()
为了加入和交叉填充,我们必须限制那些被视为外键一部分的列。
可变主键/更新级联
当实体的主键发生更改时,也必须更新引用主键的相关项。对于强制引用完整性的数据库,最好的策略是使用数据库的on update cascade功能,以便将主键更改传播到引用的外键-除非将约束标记为“可延迟”,即在事务完成之前不强制执行,否则值不会在任何时候失去同步。
它是 强烈推荐 寻求使用具有可变值的自然主键的应用程序使用 ON UPDATE CASCADE
数据库的功能。说明这一点的示例映射是:
class User(Base): __tablename__ = 'user' __table_args__ = {'mysql_engine': 'InnoDB'} username = Column(String(50), primary_key=True) fullname = Column(String(100)) addresses = relationship("Address") class Address(Base): __tablename__ = 'address' __table_args__ = {'mysql_engine': 'InnoDB'} email = Column(String(50), primary_key=True) username = Column(String(50), ForeignKey('user.username', onupdate="cascade") )
上面,我们举例说明 onupdate="cascade"
上 ForeignKey
对象,我们还说明 mysql_engine='InnoDB'
在mysql后端设置,确保 InnoDB
使用支持引用完整性的引擎。使用sqlite时,应使用中描述的配置启用引用完整性。 外键支持 .
参见
在具有ORM关系的DELETE cascade中使用外键 -支持删除带关系的级联
mapper.passive_updates
- similar feature on mapper()
在没有外键支持的情况下模拟有限更新级联
在这些情况下,如果使用了不支持引用完整性的数据库,并且使用了具有可变值的自然主键,则SQLAlchemy提供了一个特性,以便允许将主键值传播到已引用的外键到 有限的 扩展,方法是针对立即引用其值已更改的主键列的外键列发出UPDATE语句。没有引用完整性功能的主要平台是mysql,当 MyISAM
使用存储引擎,当 PRAGMA foreign_keys=ON
未使用pragma。Oracle数据库也不支持 ON UPDATE CASCADE
但由于它仍然强制引用完整性,需要将约束标记为可延迟,以便SQLAlchemy可以发出UPDATE语句。
通过设置 relationship.passive_updates
旗到 False
,最好是一对多或多对多 relationship()
. 当“updates”不再是“passive”时,这表示SQLAlchemy将为父对象引用的集合中引用的对象单独发出update语句,该对象的主键值将发生更改。这还意味着,如果集合不在本地存在,那么它将被完全加载到内存中。
我们以前的映射使用 passive_updates=False
看起来像:
class User(Base): __tablename__ = 'user' username = Column(String(50), primary_key=True) fullname = Column(String(100)) # passive_updates=False *only* needed if the database # does not implement ON UPDATE CASCADE addresses = relationship("Address", passive_updates=False) class Address(Base): __tablename__ = 'address' email = Column(String(50), primary_key=True) username = Column(String(50), ForeignKey('user.username'))
关键限制 passive_updates=False
包括:
它在update cascade上的性能比direct数据库差得多,因为它需要使用select完全预加载受影响的集合,还必须针对这些值发出update语句,它将尝试在“批处理”中运行,但仍在dbapi级别按行运行。
该功能不能“层叠”多个级别。也就是说,如果映射X有一个外键引用映射Y的主键,那么映射Y的主键本身就是映射Z的外键,
passive_updates=False
无法层叠主键值的更改Z
到X
.配置
passive_updates=False
只有关系的多对一方不会产生完全效果,因为工作单元仅通过当前标识映射搜索可能引用具有可变主键的对象,而不是整个数据库。
实际上除了Oracle以外的所有数据库都支持 ON UPDATE CASCADE
,强烈建议 ON UPDATE CASCADE
支持用于使用自然和可变主键值的情况。