SQLAlchemy - MapperExtension.before_delete未被调用

1 投票
1 回答
873 浏览
提问于 2025-04-15 14:41

我有一个关于SQLAlchemy的问题。我有一个数据库,里面有一些项目(Items),每个项目都有多个记录(1:n)。这些记录部分存储在数据库中,但它们在文件系统上也有一个对应的文件(1:1)。

我想做的是,当记录从数据库中删除时,也要删除对应的文件。所以我写了以下的MapperExtension:

class _StoredRecordEraser(MapperExtension):
    def before_delete(self, mapper, connection, instance):
        instance.erase()

下面的代码创建了一个实验性的设置(完整代码在这里:test.py):

session = Session()

i1 = Item(id='item1')
r11 = Record(id='record11', attr='1')
i1.records.append(r11)
r12 = Record(id='record12', attr='2')
i1.records.append(r12)
session.add(i1)
session.commit()

最后,我的问题是……下面的代码运行得很好,old.erase()方法被调用了:

session = Session()
i1 = session.query(Item).get('item1')
old = i1.records[0]
new = Record(id='record13', attr='3')
i1.records.remove(old)
i1.records.append(new)
session.commit()

但是当我把一个新记录的id改成record11,而这个record11已经在数据库里了,但它不是同一个项目(attr=3),那么old.erase()就没有被调用。有没有人知道为什么?

谢谢

1 个回答

4

在一次操作中,如果你删除和插入了两个记录,而这两个记录的主键是一样的,系统现在会把这个操作当作一次更新来处理。这其实不是最理想的做法——它应该先删除再插入,这样和这些记录相关的各种事件才能按预期触发(不仅仅是映射器的扩展方法,还有数据库层面的默认设置)。不过,flush() 这个过程是固定的,它会先执行插入和更新,然后再执行删除。作为一种解决方法,你可以在删除操作后先执行一次 flush(),然后再执行一次 flush() 来进行添加或插入。

关于 flush() 当前的行为,我尝试去拆解这个过程,但发现变得非常复杂——依赖于删除的插入操作必须在删除之后执行,而依赖于插入的更新操作又必须在插入之前执行。最终,unitofwork 模块可能需要进行大规模重写,以便将所有的插入、更新和删除操作视为一个依赖关系的单一流,这样可以进行拓扑排序。这将简化执行语句的顺序方法,尽管所有新的系统需要根据服务器级别的默认设置来同步行之间的数据,如果发现“更简单”的方法在排序插入语句时花费了太多时间,可能会重新引入复杂性,因为在 ORM 层面上这些插入语句是已知的,不需要相互排序。目前的拓扑排序处理的粒度要粗一些。

撰写回答