django:预取 GenericForeignKey 的相关对象

2024-04-06

假设我有一个模型Box with a GenericForeignKey指向任一Apple实例或Chocolate实例。Apple and Chocolate,反过来,有外键Farm and Factory, 分别。我想显示一个列表Boxes,我需要访问它Farm and Factory。如何以尽可能少的数据库查询来完成此操作?

最小说明示例:

class Farm(Model):
    ...

class Apple(Model):
    farm = ForeignKey(Farm)
    ...

class Factory(Model):
    ...

class Chocolate(Model):
    factory = ForeignKey(Factory)
    ...

class Box(Model)
    content_type = ForeignKey(ContentType)
    object_id = PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    ...

    def __unicode__(self):
        if self.content_type == ContentType.objects.get_for_model(Apple):
            apple = self.content_object
            return "Apple {} from Farm {}".format(apple, apple.farm)
        elif self.content_type == ContentType.objects.get_for_model(Chocolate):
            chocolate = self.content_object
            return "Chocolate {} from Factory {}".format(chocolate, chocolate.factory)

这是我尝试过的一些事情。在所有这些例子中,N是盒子的数量。查询计数假设ContentTypes for Apple and Chocolate已经被缓存了,所以get_for_model()调用不会到达数据库。

1)天真:

print [box for box in Box.objects.all()]

这确实1(获取盒子)+N(为每个盒子取苹果或巧克力)+N(获取每个苹果的农场和每个巧克力的工厂)查询。

2) select_related在这里没有帮助,因为Box.content_object is a GenericForeignKey.

3)从 django 1.4 开始,prefetch_related可以取GenericForeignKeys.

print [box for box in Box.objects.prefetch_related('content_object').all()]

这确实1(获取盒子)+2(为所有盒子获取苹果和巧克力)+N(获取每个苹果的农场和每个巧克力的工厂)查询。

4)显然prefetch_related不够聪明,无法遵循 GenericForeignKeys 的foreignkeys。如果我尝试:

print [box for box in Box.objects.prefetch_related( 'content_object__farm', 'content_object__factory').all()]

它正确地抱怨说Chocolate对象没有farm场,反之亦然。

5)我可以这样做:

apple_ctype = ContentType.objects.get_for_model(Apple)
chocolate_ctype = ContentType.objects.get_for_model(Chocolate)
boxes_with_apples = Box.objects.filter(content_type=apple_ctype).prefetch_related('content_object__farm')
boxes_with_chocolates = Box.objects.filter(content_type=chocolate_ctype).prefetch_related('content_object__factory')

这确实1(获取盒子)+2(为所有盒子获取苹果和巧克力)+2(获取所有苹果的农场和所有巧克力的工厂)查询。缺点是我必须对两个查询集进行合并和排序(boxes_with_apples, boxes_with_chocolates)手动。在我的实际应用程序中,我在分页的 ModelAdmin 中显示这些框。如何将该解决方案集成到那里并不明显。也许我可以编写一个自定义分页器来透明地进行此缓存?

6)我可以根据以下内容拼凑一些东西this http://blog.roseman.org.uk/2010/02/22/django-patterns-part-4-forwards-generic-relations/这也执行 O(1) 查询。但我不想搞乱内部结构(_content_object_cache)如果我能避免的话。

总之:打印 Box 需要访问 GenericForeignKey 的外键。如何在 O(1) 查询中打印 N 个盒子?(5) 是我能做的最好的事情,还是有更简单的解决方案?

奖励积分:您将如何重构此数据库模式以使此类查询更容易?


您可以手动实现类似的东西prefetch_selected并使用 Django 的select_related方法,这将在数据库查询中进行连接。

apple_ctype = ContentType.objects.get_for_model(Apple)
chocolate_ctype = ContentType.objects.get_for_model(Chocolate)
boxes = Box.objects.all()
content_objects = {}
# apples
content_objects[apple_ctype.id] = Apple.objects.select_related(
    'farm').in_bulk(
        [b.object_id for b in boxes if b.content_type == apple_ctype]
    )
# chocolates
content_objects[chocolate_ctype.id] = Chocolate.objects.select_related(
    'factory').in_bulk(
        [b.object_id for b in boxes if b.content_type == chocolate_ctype]
    )

这应该只进行 3 个查询(get_for_model查询被省略)。这in_bulk https://docs.djangoproject.com/en/1.4/ref/models/querysets/#django.db.models.query.QuerySet.in_bulk方法返回格式为 {id: model} 的字典。因此,要获取您的 content_object,您需要如下代码:

content_obj = content_objects[box.content_type_id][box.object_id]

但是我不确定这个代码是否会比你的更快O(5)解决方案,因为它需要对框查询集进行额外的迭代,并且它还生成一个带有WHERE id IN (...)陈述。

但是,如果您仅按框模型中的字段对框进行排序,则可以填写content_objects分页后的字典。但你需要通过content_objects to __unicode__不知何故。

您将如何重构此数据库模式以使此类查询更容易?

我们有类似的结构。我们储存content_object in Box,但不是object_id and content_object we use ForeignKey(Box) in Apple and Chocolate. In Box我们有一个get_object方法返回 Apple 或 Chocolate 模型。在这种情况下我们可以使用select_related,但在大多数用例中,我们按 content_type 过滤框。所以我们和你的第五个选项有同样的问题。但是我们在 Django 1.2 上开始我们的项目时没有 prefetch_selected。

如果您将农场/工厂重命名为一些常用名称,例如创建者,那么 prefetch_lated 会起作用吗?

关于你的option 6

我不能说任何反对填充的话_content_object_cache。 如果您不喜欢处理内部结构,您可以填充自定义属性,然后使用

apple = getattr(self, 'my_custop_prop', None)
if apple is None:
    apple = self.content_object
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

django:预取 GenericForeignKey 的相关对象 的相关文章

随机推荐