About
RSS

Bit Focus


对象炼金术 - SQLAlchemy 中关系的级联

Posted at 2012-03-13 04:44:15 | Updated at 2018-05-25 13:27:40

    在上一节中谈到了如何把有关联的对象一起塞进数据表中, 现在来试试从数据表里面取出一条数据然后删掉.
    在之前代码的基础上添加下面的函数
def remove_album(name):
    session = Session()
    try:
        for a in session.query(Album).filter(Album.name == name).all():
            session.delete(a)
        session.flush()
        session.commit()
    finally:
        session.close()
    在这个函数中, 查询 Album 类型, 然后调用查询对象的 filter 函数, 指定列 name 的值严格等于该 name 参数. 接下来, 将查询得到的对象逐个删除.
    来看看搞起来如何
if __name__ == '__main__':
    save()
    remove_album('stella musica')
    list_all()
    嗯, 看起来还不错的样子.

    那么, 接下来试试删 Artist
def remove_artist(name):
    session = Session()
    try:
        for a in session.query(Artist).filter(Artist.name == name).all():
            session.delete(a)
        session.flush()
        session.commit()
    finally:
        session.close()

if __name__ == '__main__':
    save()
    remove_artist('katou emiri')
    list_all()
于是乎就悲剧了.
    崩盘之前的几行输出似乎是这样子的
= Albums =
stella musica
+ Artist: aki misawa
hoshikage no ama no hara
+ Artist: aki misawa
jump!
+ Artist:
    也就是说, remove_artist 确实从数据库中删去了名字为 'katou emiri' 的那个值, 因此名为 'jump!' 的那个 Album 实例的 artist 域成了 None, 悲剧就发生了.

    要解决这个问题, 就得在 AlbumArtist 的关联关系上动动手脚, 加上级联信息.
class Artist(Base):
    __tablename__ = 'artist'
    artist_id = sqla.Column('id', sqla.Integer, primary_key=True)
    name = sqla.Column('name', sqla.String)

class Album(Base):
    __tablename__ = 'album'
    album_id = sqla.Column('id', sqla.Integer, primary_key=True)
    name = sqla.Column('name', sqla.String)
    artist_id = sqla.Column('artist', sqla.ForeignKey('artist.id'))

Artist.albums = sqlorm.relationship(Album)
Album.artist = sqlorm.relationship(Artist)

Album.artist = sqlorm.relationship(
        Artist, backref=sqlorm.backref('albums', cascade='all'))
再来运行一次就好了.
    这一次删去了 ArtistAlbumrelationship, 同时为 AlbumArtistrelationship 添加了参数 backref, 表示反向引用到 Artist 实例的 albums 成员 (上面删去 Artist.albums 这个关联就是为了给 backref 腾个位置出来, 所以 list_all 或者其它任何函数都可以查询得到的 Artist 实例的 albums 成员), 并且在删除 Artist 实例时级联删除对应的 Album 实例集, 而反过来删则不会.

    最后来尝试修改数据. 下面是一个示例函数
def modify():
    session = Session()
    try:
        artists = session.query(Artist).filter(Artist.name.like('%isa%')).all()
        artist = artists[0]
        artist.name = 'misawa aki'
        artist.albums = filter(lambda a: a.name.startswith('ho'), artist.albums)
        artist.albums.append(Album(name='natuhana no kageokuri', artist=artist))
        session.flush()
        session.commit()
    finally:
        session.close()

if __name__ == '__main__':
    save()
    modify()
    list_all()
再一次悲剧, 悲剧之前的输出如下
= Artists =
misawa aki
- Album: hoshikage no ama no hara
- Album: natuhana no kageokuri
katou emiri
- Album: jump!

= Albums =
stella musica
+ Artist:
    从中可以看出,名为 'stella musica'Album 实例确实已经跟名为 misawa aki (这个实例对象的名字修改也完成了) 的实例脱离关系了, 所以在前一段的输出看不到, 但它实际上还没有从数据库中删去, 因此取得该对象时, 它的 artist 域为 None.
    为了确保从反过来, 从集合中删除实例的引用也同步删除表中数据, 需要修改级联方式, 添加 'delete-orphan' 项. 另外, 既然 Artist.albums 这个项目已经移除了, 所以索性把 Album.artist 直接放回 Album 类声明中去好了.
class Album(Base):
    __tablename__ = 'album'
    album_id = sqla.Column('id', sqla.Integer, primary_key=True)
    name = sqla.Column('name', sqla.String)
    artist_id = sqla.Column('artist', sqla.ForeignKey('artist.id'))
    artist = sqlorm.relationship(
            Artist, backref=sqlorm.backref('albums',
                                           cascade='all,delete-orphan'))
modify() 就没问题了.

PS: 如果需求某个表中建立多个外键, 且它们引用相同的另一张表, 请移步这里.

Post tags:   SQLAlchemy  ORM  Python  Tutorial

Leave a comment:




Creative Commons License Your comment will be licensed under
CC-NC-ND 3.0


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site