【问题标题】:EmbeddedDocumentSerializer runs query for every ReferenceFieldEmbeddedDocumentSerializer 为每个 ReferenceField 运行查询
【发布时间】:2016-09-25 19:15:12
【问题描述】:

我有以下模型和序列化程序,目标是当序列化程序运行时只有一个查询:

型号:

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 中删除受让人字段,则只有一个查询。

作为一种解决方法,我尝试使用 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 而不为每个引用运行单独的查询?

【问题讨论】:

    标签: mongodb serialization django-rest-framework mongoengine


    【解决方案1】:

    我最终找到了一个解决方法,而不是使用 ReferenceField。相反,我使用的是 ObjectIdField:

    #user = ReferenceField("MongoUser", required=True) # Removed now
    user = ObjectIdField(required=True)
    

    并改变赋值如下:

    -        if assignee.user == MongoUser:
    +        if assignee.user == MongoUser.id:
    

    这不是最好的方法 - 我们没有使用 ReferenceField 功能,但它比在序列化程序中创建 30 个查询要好。

    最好的问候, 克里斯蒂安

    【讨论】:

      【解决方案2】:

      这是一个非常有趣的问题,我认为它与 Mongoengine 的 DeReference 政策有关:https://github.com/MongoEngine/mongoengine/blob/master/mongoengine/dereference.py

      也就是说,您的 mongoengine 文档有一个方法 MongoUser.objects.select_related()max_depth 参数,该参数应该足够大,以便 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 中的 current implementationReferenceField 不够聪明,无法适当地处理这些查询集。可能是ComboReferenceField 会起作用吗?

      不过,我还没有使用过这个功能,也没有足够的时间亲自体验这些设置,所以如果你能分享你的发现,我将不胜感激。

      【讨论】:

      • 在我的情况下使用 ComboReferenceField 不起作用,我使用的是来自 mongoengine 的 ReferenceField 而不是来自 rest_framework_mongoengine 的 dbref=False 所以我可以以这种方式保存我的分配 Assignee(id=bson.objectid.ObjectId(), user=MongoUser) 并使用自动取消引用访问(懒惰)功能。对于 max_depth 方式:使用:user = MongoUser.objects.select_related(max_depth=5) 或 0 或 3 没有区别。
      猜你喜欢
      • 1970-01-01
      • 2011-10-21
      • 2016-06-23
      • 1970-01-01
      • 2015-11-30
      • 2013-03-12
      • 1970-01-01
      • 1970-01-01
      • 2012-04-17
      相关资源
      最近更新 更多