假设我有一个模型Box
,GenericForeignKey
它指向一个Apple
实例或一个Chocolate
实例。Apple
和Chocolate
又分别具有到Farm
和Factory
的外键。我想显示一个Box
es 列表,为此我需要访问Farm
和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是 Box 的数量。查询计数假定ContentType
sApple
和Chocolate
已经被缓存,因此get_for_model()
调用不会命中数据库。
1)天真:
print [box for box in Box.objects.all()]
这会执行1(获取 Boxes)+ N(为每个 Box 获取 Apple 或 Chocolate)+ N(为每个 Apple 获取 Farm,为每个 Chocolate 获取 Factory)查询。
2)select_related
在这里没有帮助,因为Box.content_object
是GenericForeignKey
.
3) 从 django 1.4 开始,prefetch_related
可以 fetch 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)我可以基于此拼凑一些也可以进行 O(1) 查询的东西。_content_object_cache
但是,如果可以避免的话,我宁愿不要弄乱内部( )。
总而言之:打印一个 Box 需要访问 GenericForeignKey 的 ForeignKeys。如何在 O(1) 查询中打印 N 个框?(5) 是我能做的最好的,还是有更简单的解决方案?
加分项:您将如何重构此 DB 模式以使此类查询更容易?