EmbeddedDocumentSerializer 为每个 ReferenceField 运行查询
EmbeddedDocumentSerializer runs query for every ReferenceField
我有以下模型和序列化器,目标是当序列化器 运行s 只有一个查询时:
型号:
class Assignee(EmbeddedDocument):
id = ObjectIdField(primary_key=True)
assignee_email = EmailField(required=True)
assignee_first_name = StringField(required=True)
assignee_last_name = StringField()
assignee_time = DateTimeField(required=True, default=datetime.datetime.utcnow)
user = ReferenceField('MongoUser', required=True)
user_id = ObjectIdField(required=True)
class MongoUser(Document):
email = EmailField(required=True, unique=True)
password = StringField(required=True)
first_name = StringField(required=True)
last_name = StringField()
assignees= EmbeddedDocumentListField(Assignee)
序列化程序:
class MongoUserSerializer(DocumentSerializer):
assignees = AssigneeSerializer(many=True)
class Meta:
model = MongoUser
fields = ('id', 'email', 'first_name', 'last_name', 'assignees')
depth = 2
class AssigneeSerializer(EmbeddedDocumentSerializer):
class Meta:
model = Assignee
fields = ('assignee_first_name', 'assignee_last_name', 'user')
depth = 0
检查 mongo 探查器时,我对 MongoUser 文档有 2 个查询。如果我从 MongoUserSerializer 中删除 assignees 字段,则只有一个查询。
作为解决方法,我尝试使用 user_id 字段仅存储 ObjectId 并将 AssigneeSerializer 更改为:
class AssigneeSerializer(EmbeddedDocumentSerializer):
class Meta:
model = Assignee
fields = ('assignee_first_name', 'assignee_last_name', 'user_id')
depth = 0
但同样有 2 个查询。我认为序列化程序 EmbeddedDocumentSerializer 获取 ReferenceField 和
的所有字段和查询
fields = ('assignee_first_name', 'assignee_last_name', 'user_id')
查询后有效。
如何在序列化时使用 ReferenceField 而不是 运行 对每个引用单独查询?
这是一个非常有趣的问题,我认为它与 Mongoengine 的 DeReference 策略有关:https://github.com/MongoEngine/mongoengine/blob/master/mongoengine/dereference.py。
也就是说,您的 mongoengine 文档有一个带有 max_depth
参数的方法 MongoUser.objects.select_related()
,该方法应该足够大,以便 Mongoengine 遍历 3 个深度级别:MongoUser
->assignees
->Assignee
->user
并缓存当前 MongoUser
实例的所有相关 MongoUser
对象。也许,我们应该在 DRF-Mongoengine 的 DocumentSerializers 中的某处调用此方法来预取关系,但目前我们没有这样做。
参见 this post 关于经典 DRF + Django ORM 的解释,如何通过在经典 DRF 中进行预取来解决 N+1 请求问题。基本上,您需要覆盖 ModelViewSet
的 get_queryset()
方法才能使用 select_related()
方法:
from rest_framework_mongoengine.viewsets import ModelViewSet
class MongoUserViewSet(ModelViewSet):
def get_queryset(self):
queryset = MongoUser.objects.all()
# Set up eager loading to avoid N+1 selects
queryset.select_related(max_depth=3)
return queryset
不幸的是,我认为 DRF-Mongoengine 中 ReferenceField
的 current implementation 不够聪明,无法适当地处理这些查询集。可能 ComboReferenceField
会起作用?
不过,我从来没有使用过这个功能,也没有足够的时间自己玩这些设置,所以如果你能分享你的发现,我将不胜感激。
我最终找到了解决方法,没有使用 ReferenceField。相反,我正在使用 ObjectIdField:
#user = ReferenceField("MongoUser", required=True) # Removed now
user = ObjectIdField(required=True)
并更改赋值如下:
- if assignee.user == MongoUser:
+ if assignee.user == MongoUser.id:
这不是最好的方法 - 我们没有使用 ReferenceField 功能,但它比在序列化程序中创建 30 个查询要好。
此致,
克里斯蒂安
我有以下模型和序列化器,目标是当序列化器 运行s 只有一个查询时:
型号:
class Assignee(EmbeddedDocument):
id = ObjectIdField(primary_key=True)
assignee_email = EmailField(required=True)
assignee_first_name = StringField(required=True)
assignee_last_name = StringField()
assignee_time = DateTimeField(required=True, default=datetime.datetime.utcnow)
user = ReferenceField('MongoUser', required=True)
user_id = ObjectIdField(required=True)
class MongoUser(Document):
email = EmailField(required=True, unique=True)
password = StringField(required=True)
first_name = StringField(required=True)
last_name = StringField()
assignees= EmbeddedDocumentListField(Assignee)
序列化程序:
class MongoUserSerializer(DocumentSerializer):
assignees = AssigneeSerializer(many=True)
class Meta:
model = MongoUser
fields = ('id', 'email', 'first_name', 'last_name', 'assignees')
depth = 2
class AssigneeSerializer(EmbeddedDocumentSerializer):
class Meta:
model = Assignee
fields = ('assignee_first_name', 'assignee_last_name', 'user')
depth = 0
检查 mongo 探查器时,我对 MongoUser 文档有 2 个查询。如果我从 MongoUserSerializer 中删除 assignees 字段,则只有一个查询。
作为解决方法,我尝试使用 user_id 字段仅存储 ObjectId 并将 AssigneeSerializer 更改为:
class AssigneeSerializer(EmbeddedDocumentSerializer):
class Meta:
model = Assignee
fields = ('assignee_first_name', 'assignee_last_name', 'user_id')
depth = 0
但同样有 2 个查询。我认为序列化程序 EmbeddedDocumentSerializer 获取 ReferenceField 和
的所有字段和查询fields = ('assignee_first_name', 'assignee_last_name', 'user_id')
查询后有效。 如何在序列化时使用 ReferenceField 而不是 运行 对每个引用单独查询?
这是一个非常有趣的问题,我认为它与 Mongoengine 的 DeReference 策略有关:https://github.com/MongoEngine/mongoengine/blob/master/mongoengine/dereference.py。
也就是说,您的 mongoengine 文档有一个带有 max_depth
参数的方法 MongoUser.objects.select_related()
,该方法应该足够大,以便 Mongoengine 遍历 3 个深度级别:MongoUser
->assignees
->Assignee
->user
并缓存当前 MongoUser
实例的所有相关 MongoUser
对象。也许,我们应该在 DRF-Mongoengine 的 DocumentSerializers 中的某处调用此方法来预取关系,但目前我们没有这样做。
参见 this post 关于经典 DRF + Django ORM 的解释,如何通过在经典 DRF 中进行预取来解决 N+1 请求问题。基本上,您需要覆盖 ModelViewSet
的 get_queryset()
方法才能使用 select_related()
方法:
from rest_framework_mongoengine.viewsets import ModelViewSet
class MongoUserViewSet(ModelViewSet):
def get_queryset(self):
queryset = MongoUser.objects.all()
# Set up eager loading to avoid N+1 selects
queryset.select_related(max_depth=3)
return queryset
不幸的是,我认为 DRF-Mongoengine 中 ReferenceField
的 current implementation 不够聪明,无法适当地处理这些查询集。可能 ComboReferenceField
会起作用?
不过,我从来没有使用过这个功能,也没有足够的时间自己玩这些设置,所以如果你能分享你的发现,我将不胜感激。
我最终找到了解决方法,没有使用 ReferenceField。相反,我正在使用 ObjectIdField:
#user = ReferenceField("MongoUser", required=True) # Removed now
user = ObjectIdField(required=True)
并更改赋值如下:
- if assignee.user == MongoUser:
+ if assignee.user == MongoUser.id:
这不是最好的方法 - 我们没有使用 ReferenceField 功能,但它比在序列化程序中创建 30 个查询要好。
此致, 克里斯蒂安