假设我有一个模型Box
with a GenericForeignKey
指向任一Apple
实例或Chocolate
实例。Apple
and Chocolate
,反过来,有外键Farm
and Factory
, 分别。我想显示一个列表Box
es,我需要访问它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是盒子的数量。查询计数假设ContentType
s 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
可以取GenericForeignKey
s.
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) 是我能做的最好的事情,还是有更简单的解决方案?
奖励积分:您将如何重构此数据库模式以使此类查询更容易?