Django prefetch_related无法从带有manytomanyfield的foreignkey预取数据的问题

6
例如,在Django 1.8中:
class A(models.Model):
    x = models.BooleanField(default=True)

class B(models.Model):
    y = models.ManyToManyField(A)

class C(models.Model):
    z = models.ForeignKey(A)

在这种情况下,C.objects.all().prefetch_related('z__b_set')无法起作用。
有没有一种方法可以预取我需要的信息,使得c[0].z.b_set.all()可以正常工作,而不需要进行额外的查询?

尝试使用select_related - 301_Moved_Permanently
这将导致一个 FieldError: Invalid field name(s) given in select_related: 'b_set'. - Sem
1
抱歉,我总是混淆这两个概念,在发布之前甚至没有重新查看文档。select_related用于检索单个关联对象,而不是一组对象。无论如何,当你说prefetch_related不起作用时...你能详细说明一下吗?发生了什么事情,你期望得到什么? - 301_Moved_Permanently
C.objects.all().prefetch_related('z__b_set')并没有像它应该的那样改变查询。查询字符串(str(queryset.query))与C.objects.all()完全相同。 - Sem
C.objects.all().select_related('z').prefetch_related('z__b_set') 是什么意思? - 301_Moved_Permanently
这将把 z 作为 LEFT JOIN 添加到查询中,但是 b_set 仍未包含在查询中。 - Sem
2个回答

9

您可以使用 select_related 一次性地跟随关系的第一部分(C.zForeignKey):

C.objects.all().select_related('z').prefetch_related('z__b_set')

然而,文档中已经说明,prefetch_related 部分至少需要两个查询来完成:

另一方面,prefetch_related会为每个关系执行单独的查找,并在Python中进行“连接”。这允许它预取无法使用select_related完成的多对多和多对一对象,以及select_related支持的外键和一对一关系。它还支持GenericRelation和GenericForeignKey的预取。


你是对的,str(queryset.query) 不是正确的验证方式。不幸的是,我的问题仍然存在。C.z.b_set.get(id=1) 仍会产生一个新的查询。 - Sem
C.z.b_set.all() 却不行!我猜我得在 Python 中过滤它。 - Sem
C.z.b_set.get(id=1) 是什么意思?它是否等同于 cs = C.objects.all().select_related('z').prefetch_related('z__b_set'); cs[0].z.b_set.get(id=1) - 301_Moved_Permanently
C.z.b_set.get(id=1) 的意思是我想要具有id 1的集合中的B。这会添加一个查询。如果我想要在不使用查询的情况下进行过滤,我需要使用all(),因为它是预取的,然后在Python中进行过滤。感谢您的所有帮助Mathias :) - Sem
1
@Joseph 我猜这个五年前的答案可能有点过时了;但我还没有尝试过Django 2来更新它。如果你弄清楚了,把它作为你自己的答案添加可能会有帮助。 - 301_Moved_Permanently
显示剩余2条评论

1

您需要在B中明确设置related_name并进行迁移:

class B(models.Model):
    y = models.ManyToManyField(A, related_name="bs")

好主意,但是改名并不会改变任何功能。 - Sem
1
我在升级到1.8版本后,遇到了许多对多关系的类似问题,并通过定义related_name解决了它。我认为这也可能有助于你的情况 :) - nima

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接