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 请求问题。基本上,您需要覆盖 ModelViewSetget_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 中 ReferenceFieldcurrent 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 个查询要好。

此致, 克里斯蒂安