Django一对多与多对一序列化器及查询实现问题
优化嵌套关联数据的序列化与查询方案
问题背景
数据关系为:一个城市对应多个建筑,一个建筑可对应零到多个设备。需通过城市主键请求数据时,返回包含下属所有建筑及对应设备的嵌套结构。当前采用嵌套循环获取关联数据,效率低下,需优化序列化器编写与数据库查询方式。
模型定义:
class City(models.Model): # 自定义字段,例如名称等 name = models.CharField(max_length=100) class Building(models.Model): cityId = models.ForeignKey(City, on_delete=models.CASCADE) # 自定义字段 name = models.CharField(max_length=100) class Device(models.Model): buildingId = models.ForeignKey(Building, on_delete=models.CASCADE) # 自定义字段 name = models.CharField(max_length=100)
现有序列化器仅包含单表字段:
class CitySerializer(serializers.ModelSerializer): class Meta: model = City fields = ['id', 'name'] class BuildingSerializer(serializers.ModelSerializer): class Meta: model = Building fields = ['id', 'cityId', 'name'] class DeviceSerializer(serializers.ModelSerializer): class Meta: model = Device fields = ['id', 'buildingId', 'name']
优化方案
1. 嵌套序列化器实现层级数据返回
通过嵌套序列化器自动处理关联关系,无需手动拼接数据:
第一步:给外键添加反向关联名称(related_name)
修改模型外键定义,简化反向查询逻辑:
class Building(models.Model): # 添加related_name,通过city.buildings可直接获取该城市的所有建筑 cityId = models.ForeignKey(City, on_delete=models.CASCADE, related_name='buildings') name = models.CharField(max_length=100) class Device(models.Model): # 添加related_name,通过building.devices可直接获取该建筑的所有设备 buildingId = models.ForeignKey(Building, on_delete=models.CASCADE, related_name='devices') name = models.CharField(max_length=100)
第二步:编写嵌套序列化器
class DeviceSerializer(serializers.ModelSerializer): class Meta: model = Device fields = ['id', 'name'] # 无需返回buildingId,已嵌套在建筑数据中 class BuildingSerializer(serializers.ModelSerializer): # 嵌套设备序列化器,many=True表示多设备,read_only=True仅用于序列化输出 devices = DeviceSerializer(many=True, read_only=True) class Meta: model = Building fields = ['id', 'name', 'devices'] # 无需返回cityId,已嵌套在城市数据中 class CitySerializer(serializers.ModelSerializer): # 嵌套建筑序列化器 buildings = BuildingSerializer(many=True, read_only=True) class Meta: model = City fields = ['id', 'name', 'buildings']
2. 预取关联数据解决N+1查询问题
使用prefetch_related链式预取关联数据,避免多次数据库查询,大幅提升性能:
在视图中查询城市数据时,一次性拉取所有关联的建筑和设备:
from rest_framework.views import APIView from rest_framework.response import Response from .models import City from .serializers import CitySerializer class CityDetailView(APIView): def get(self, request, pk): # 链式预取城市的所有建筑,以及每个建筑的所有设备 city = City.objects.prefetch_related('buildings__devices').get(pk=pk) # 如果需要对设备/建筑做过滤,可使用Prefetch精细化控制 # from django.db.models import Prefetch # city = City.objects.prefetch_related( # Prefetch('buildings', queryset=Building.objects.prefetch_related( # Prefetch('devices', queryset=Device.objects.filter(status='active')) # )) # ).get(pk=pk) serializer = CitySerializer(city) return Response(serializer.data)
内容的提问来源于stack exchange,提问作者dylan
相关产品推荐
相关产品推荐

