让我们看一下使用Pizza和Topping模型的django docs的示例。 一个披萨可能有多个配料。
如果我们进行查询:
pizzas = Pizza.objects.prefetch_related('toppings')
我们将在2个查询中获取所有比萨饼及其配料。 现在让我们假设我只想预取素食浇头(假设我们有这样的属性):
pizzas = Pizza.objects.prefetch_related(
Prefetch('toppings', queryset=Topping.objects.filter(is_vegetarian=True))
)
它做得很好,当制作这样的东西时,Django不会对每个披萨执行另一个查询:
for pizza in pizzas:
print(pizza.toppings.filter(is_vegetarian=True))
现在让我们假设我们有一个Topping模型的自定义管理器,我们决定在那里放一个方法,允许我们只过滤素食浇头,如上面的代码示例:
class ToppingManager(models.Manager):
def filter_vegetarian(self):
return self.filter(is_vegetarian=True)
现在我使用来自manager的方法创建一个新查询并预取自定义查询集:
pizzas = Pizza.objects.prefetch_related(
Prefetch('toppings', queryset=Topping.objects.filter_vegetarian()))
尝试执行我的代码:
for pizza in pizzas:
print(pizza.toppings.filter_vegeterian())
我为循环的每次迭代得到一个新的查询。 这是我的问题。为什么? 这两个结构都返回相同的类型对象,即queryset:
Topping.objects.filter_vegetarian()
Topping.objects.filter(is_vegetarian=True)
答案 0 :(得分:4)
我还没有直接对此进行测试,但是你不应该在循环中再次调用方法或过滤器,因为prefetch_related已经附加了数据。所以这些都应该有效:
pizzas = Pizza.objects.prefetch_related(
Prefetch('toppings', queryset=Topping.objects.filter(is_vegetarian=True))
)
for pizza in pizzas:
print(pizza.toppings.all()) # uses prefetched queryset
或
pizzas = Pizza.objects.prefetch_related(
Prefetch('toppings', queryset=Topping.objects.filter_vegetarian(),
to_attr="veg_toppings"))
for pizza in pizzas:
print(pizza.toppings.veg_toppings)
您的示例不起作用,因为它们调用另一个查询集,并且无法将其与预取的查询集进行比较以确定它是否相同。
它也是如此in the docs:
prefetch_related('toppings')
隐含pizza.toppings.all()
,但pizza.toppings.filter()
是一个新的不同查询。预取缓存在这里无济于事;实际上它会损害性能,因为您已经完成了未使用的数据库查询。
和
在过滤预取结果时,建议使用to_attr,因为它比将过滤结果存储在相关管理器缓存中更不明确。
答案 1 :(得分:1)
此实施:
class ToppingManager(models.Manager):
def filter_vegetarian(self):
return self.filter(is_vegetarian=True)
看起来不合标准。 docs看起来他们为这种懒惰的东西修改超类方法做了一种更安全的方法。如果我以该样式重写您的方法,它将如下所示:
class ToppingManager(models.Manager):
def filter_vegetarian(self):
return super(ToppingManager, self).get_queryset().filter(is_vegetarian=True)
你不会在这里严格需要super(),但是使用它会更安全,因为你应该知道你想要从models.Manager get_queryset方法开始。
在我自己的环境中对此进行简要测试,我发现它可以在Prefetch
中输入,而不会触发每个项目的查询。我没有任何理由相信这对这个问题不起作用。
但是,我也倾向于认为在webjunkie的答案中指定to_attr
也是必要的。