Django中如何在QuerySet预取并合并同一模型的两个反向外键?
解决方案
首先修正模型中的拼写错误(ForeingKey → ForeignKey):
class Task(models.Model): class Meta: db_table = "task" class Rule(models.Model): to_task = models.ForeignKey(Task, related_name="to_task_rules") from_task = models.ForeignKey(Task, related_name="from_task_rules") class Meta: db_table = "rule"
方法一:预取双关联集 + 模型属性合并去重
这种方法通过预取两个关联的Rule集合,再在Task模型中添加属性合并并去重,完全避免N+1查询问题。
1. 预取两个关联集合
查询Task时,分别预取from_task_rules和to_task_rules到不同的临时属性:
from django.db.models import Prefetch tasks = Task.objects.prefetch_related( Prefetch("from_task_rules", to_attr="_prefetched_from_rules"), Prefetch("to_task_rules", to_attr="_prefetched_to_rules") )
2. 给Task模型添加合并属性
在Task类中定义@property方法,合并两个预取的集合并去重:
from itertools import chain class Task(models.Model): class Meta: db_table = "task" @property def task_rules(self): # 合并两个预取的Rule集合,通过对象主键去重(保留首次出现的元素) all_rules = chain( getattr(self, "_prefetched_from_rules", []), getattr(self, "_prefetched_to_rules", []) ) # dict.fromkeys会自动去重,最后转回列表 return list(dict.fromkeys(all_rules))
使用方式
遍历Task对象时,直接访问task.task_rules即可得到所有唯一的关联Rule记录:
for task in tasks: for rule in task.task_rules: # 处理Rule逻辑 print(rule.id)
方法二:自定义Prefetch查询集(直接筛选关联Rule)
通过自定义Prefetch的queryset,一次性筛选出所有与当前Task关联的Rule,无需合并两个集合:
from django.db.models import Prefetch, Q, OuterRef tasks = Task.objects.prefetch_related( Prefetch( # 注意:这里需确保Rule与Task的关联逻辑匹配,也可直接基于Rule查询集预取 "task_rules", queryset=Rule.objects.filter( Q(to_task=OuterRef("pk")) | Q(from_task=OuterRef("pk")) ).distinct(), to_attr="task_rules" ) )
关键注意事项
- 预取时必须给两个关联集合指定不同的
to_attr,避免属性覆盖 - 去重逻辑依赖Rule的主键唯一性,若有其他去重需求可修改判断条件
- 方法一仅会触发2次额外查询(一次取所有from关联,一次取所有to关联),完全规避N+1问题
内容的提问来源于stack exchange,提问作者Lucas Grugru
相关产品推荐
相关产品推荐

