级联的反向等价物是什么?

6 投票
4 回答
1982 浏览
提问于 2025-04-16 12:57

我正在写一个小型的音乐数据库。学习SQL的时间已经过去很久了,我一直想尝试一下Django。但是有一件事我一直搞不明白。

现在,我的模型只有两个类,Album(专辑)和Song(歌曲)。Song类有一个外键,指向它所属的专辑。如果我删除那个Album,所有“属于”它的Song都会因为级联效应而被删除。

在我的数据库中,专辑有点像虚拟的,只有歌曲在文件系统中实际存在,专辑是根据歌曲的标签构建的。因此,只有当没有更多的歌曲指向某个专辑时,我才能知道这个专辑不再存在(因为它们在文件系统中已经不存在了)。

简单来说,我想知道如何实现反向级联,也就是说,如果没有更多的歌曲指向一个专辑,那么这个专辑也应该被删除?

4 个回答

1

我遇到过类似的问题,最后我在相册的代码里加了一个计数器。如果这个计数器的值是0,并且要执行删除操作(delete()),那么就会把这个相册对象删除掉。

另外一种解决办法是重写歌曲模型里的删除方法(delete()),或者在删除后再去删除相册。

4

这里有一个很微妙的实现细节,我觉得应该补充一下这个讨论。

假设我们有两个模型,其中一个通过外键引用另一个,像这样:

class A(models.Model):
    x = models.IntegerField()

class B(models.Model):
    a = models.ForeignKey(A, null=True, blank=True)

现在,如果我们删除了A的一个条目,B中的引用也会因为级联删除而被删除。

到这里为止,一切正常。现在我们想要反转这个行为。大家提到的明显方法是使用删除时发出的信号,所以我们可以这样做:

def delete_reverse(sender, **kwargs):
    if kwargs['instance'].a:
        kwargs['instance'].a.delete()

post_delete.connect(delete_reverse, sender=B)

这看起来很完美,实际上也确实有效!如果我们删除了B中的一个条目,相应的A也会被删除。

但问题是,这样会产生循环行为,导致出现异常:如果我们删除A中的一个项目,由于默认的级联删除行为(我们想保留这个行为),相应的B项目也会被删除,这又会导致调用delete_reverse,试图删除一个已经被删除的项目!

解决这个问题的关键是,你需要进行异常处理,以正确实现反向级联:

def delete_reverse(sender, **kwargs):
    try:
        if kwargs['instance'].a:
            kwargs['instance'].a.delete()
    except:
        pass

这段代码无论如何都能工作。希望能对一些人有所帮助。

5

你可以使用 pre_delete 这个信号,当一首歌被删除且没有其他歌曲时,就可以把专辑也删除掉。

from yourapp.models import Album, Song
from django.db.models.signals import pre_delete

def delete_parent(sender, **kwargs):
    # Here you check if there are remaining songs.
    ....

pre_delete.connect(delete_parent, sender=Song)

撰写回答