当且仅当满足最内层列表中的唯一性时,有效地更新嵌入文档列表中文档的列表字段

2024-10-11 16:28:58 发布

您现在位置:Python中文网/ 问答频道 /正文

我刚遇到一个场景,我不知道如何用文档的现有结构来解决。如下所示,我显然可以通过一些重构来解决这个问题,但是我很好奇如何才能最有效地解决这一问题,并尊重相同的结构。在

请注意这个皇后区不同于How to Do An Atomic Update on an EmbeddedDocument in a ListField in MongoEngine?

假设以下模型:

class Scans(mongoengine.EmbeddedDocument):
    peer = mongoengine.ReferenceField(Peers, required=True)
    site = mongoengine.ReferenceField(Sites, required=True)
    process_name = mongoengine.StringField(default=None)
    documents = mongoengine.ListField(mongoengine.ReferenceField('Documents'))
    is_complete = mongoengine.BooleanField(default=False)  
    to_start_at = mongoengine.DateTimeField()  
    started = mongoengine.DateTimeField()  
    finished = mongoengine.DateTimeField()


class ScanSettings(mongoengine.Document):
    site = mongoengine.ReferenceField(Sites, required=True)
    max_links = mongoengine.IntField(default=100)  
    max_size = mongoengine.IntField(default=1024)  
    mime_types = mongoengine.ListField(default=['text/html'])
    is_active = mongoengine.BooleanField(default=True)  
    created = mongoengine.DateTimeField(default=datetime.datetime.now)
    repeat = mongoengine.StringField(choices=REPEAT_PATTERN)
    scans = mongoengine.EmbeddedDocumentListField(Scans)

我想做的是插入一个ScanSettings对象,当且仅当scans字段的所有元素-扫描列表嵌入的文档列表-依次具有唯一性时?我所说的unique是指数据库级别的列表中的所有元素,而不是整个列表—这很容易。在

在纯英语中,如果在插入ScanSetting时,scans列表的任何元素都有一个scans实例,其中的文档列表是重复的,那么这样的插入不应该发生。我的意思是数据库级别的唯一性,考虑到现有的记录(如果有的话)。在

鉴于Mongo不支持同一文档中列表的所有元素的唯一性,我找到了两个解决方案:

选项A

我重构了我的“模式”,并使Scans集合从Document继承而不是从Embedded Document继承,并将ScanSettings的Scans字段更改为ReferenceFields的ListField以扫描文档。然后很容易,因为我只需要先用“Updates”和操作符“add_to_set”和选项“upsert=True”保存扫描。一旦操作被批准,保存扫描设置。我需要扫描实例的数量来插入+1个查询。在

选项B 我保留了相同的“模式”,但不知何故为扫描嵌入的文档生成了唯一的id。然后,在插入带有非空scans字段的扫描设置之前,我将获取已经存在的记录,看看在刚刚检索到的记录和要插入的记录中是否存在重复的文档objectid。 换句话说,我会通过Python而不是使用MogoneEngine/Mongodb来检查唯一性。我需要2个扫描实例来插入(读取+用add_set_操作符更新)+1个扫描设置保存

选项C 忽略唯一性。考虑到我的模型将如何构建,我非常肯定不会有重复,或者如果有,它将是可以忽略不计的。然后在阅读时处理重复的内容。对于像我这样来自关系数据库的人来说,这个解决方案让人觉得很有吸引力。在

我是一个蒙哥新手,所以我很感谢你的评论。谢谢。在

PS:我正在使用最新的MongoEngine和免费的Mongodb。在

事先非常感谢。在


Tags: to文档truedefault元素列表选项记录
1条回答
网友
1楼 · 发布于 2024-10-11 16:28:58

最后我选择了选项A,因此我将模型重构为:

a)创建一个继承自Document类的Mixin类,以添加两个方法:重写“save”,使其仅在唯一文档列表为空时允许保存;而“save”则允许在文档列表为空时保存和/或更新。这个想法是为了加强独特性。在

b)重构扫描和扫描设置,以便前者将“扫描”字段重新定义为扫描引用的列表字段,而后者将继承自文档而不是嵌入文档。在

c)实际情况是Scans和ScanSettings现在继承自Mixin类,因为这两个类需要分别保证其属性“documents”和“Scans”的唯一性。因此有了Mixin类。在

使用a)和b)我可以保证唯一性,并首先保存每个扫描实例,以便以后添加到中扫描设置.扫描以通常的方式。在

对我这样的新手来说有几点:

  1. 请注意我正在使用继承。要使其工作,您还需要在元字典中添加一个属性,以允许继承,如下面的模型所示。在
  2. 我想在我的字典里做不同的集合,所以我想把它们放在不同的集合里。在
  3. 对于save_with_university,我使用upsert=True,以便在不存在记录的情况下创建记录。我们的想法是使用“保存唯一性”与“保存、创建或更新文档(如果存在或不存在文档)”相同的方法。在
  4. 我还使用了'full'result'标志,因为我需要返回插入的最新记录的ObjectId。在
  5. Document.\u fields是一个字典,包含组成该文档的字段。实际上,我想创建一个通用的save_with_university方法,这样我就不想手动输入文档的字段,也不想重复不必要的代码,因此就产生了Mixin。在

最后是密码。它还没有完全测试,但足以让我的主要想法正确。在

class UniquenessMixin(mongoengine.Document):


def save(self, *args, **kwargs):
    try:
        many_unique = kwargs['many_unique']
    except KeyError:
        pass
    else:
        attribute = getattr(self, many_unique)
        self_name = self.__class__.__name__
        if len(attribute):
            raise errors.DbModelOperationError(f"It looks like you are trying to save a {self.__class__.__name__} "
                                               f"object with a non-empty list of {many_unique}. "
                                               f"Please use '{self_name.lower()}.save_with_uniqueness()' instead")
    return super().save(*args, **kwargs)

def save_with_uniqueness(self, many_unique):
    attribute = getattr(self, many_unique)
    self_name = self.__class__.__name__
    if not len(attribute):
        raise errors.DbModelOperationError(f"It looks like you are trying to save a {self_name} object with an "
                                           f"empty list {many_unique}. Please use '{self_name.lower()}.save()' "
                                           f"instead")

    updates, removals = self._delta()
    if not updates:
        raise errors.DbModelOperationError(f"It looks like you are trying to update '{self.__class__.__name__}' "
                                           f"but no fields were modified since this object was created")

    kwargs = {(key if key != many_unique else 'add_to_set__' + key): value for key, value in updates.items()}
    pk = bson.ObjectId() if not self.id else self.id
    result = self.__class__.objects(id=pk).update(upsert=True, full_result=True, **kwargs)

    try:
        self.id = result['upserted']
    except KeyError:
        pass
    finally:
        return self.id

meta = {'allow_inheritance': True, 'abstract': True}

class Scans(UniquenessMixin):

    peer = mongoengine.ReferenceField(Peers, required=True)
    site = mongoengine.ReferenceField(Sites, required=True)
    process_name = mongoengine.StringField(default=None)
    documents = mongoengine.ListField(mongoengine.ReferenceField('Documents'))
    is_complete = mongoengine.BooleanField(default=False)  
    to_start_at = mongoengine.DateTimeField()  
    started = mongoengine.DateTimeField()  
    finished = mongoengine.DateTimeField()

    meta = {'collection': 'Scans'}


class ScanSettings(UniquenessMixin):

       site = mongoengine.ReferenceField(Sites, required=True)
    max_links = mongoengine.IntField(default=100)  
    max_size = mongoengine.IntField(default=1024)  
    mime_types = mongoengine.ListField(default=['text/html'])
    is_active = mongoengine.BooleanField(default=True)  
    created = mongoengine.DateTimeField(default=datetime.datetime.now)
    repeat = mongoengine.StringField(choices=REPEAT_PATTERN)
    scans = mongoengine.ListField(mongoengine.ReferenceField(Scans))

    meta = {'collection': 'ScanSettings'}

相关问题 更多 >

    热门问题