我刚遇到一个我不知道如何解决文档现有结构的情况。如下所示,我显然可以通过一些重构来解决此问题,但我很好奇如何尽可能有效地解决此问题并尊重相同的结构。
请注意,该问题与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对象?唯一的意思是列表中数据库级别的所有元素,而不是整个列表,这很容易。
用简单的英语来说,如果在插入ScanSetting时,扫描列表的任何元素都具有一个扫描实例,而该扫描实例具有重复的文档列表,则不应进行这种插入。我指的是数据库级别的唯一性,考虑到现有记录(如果有)。
鉴于Mongo在同一文档中不支持列表中所有元素的唯一性,我发现了两种解决方案:
选项A
我重构了“模式”,并使Scans集合继承自Document而不是Embedded文档,并将ScanSettings的scans字段更改为ReferenceFields的ListField来扫描文档。这样就很容易了,因为我只需要首先使用带有操作符“ add_to_set”和选项“ upsert = True”的“更新”来保存扫描。然后,一旦操作获得批准,请保存ScanSettings。我将需要插入1个查询的扫描实例数。
选项B 我保持相同的“模式”,但是以某种方式为Scans嵌入式文档生成了唯一的ID。然后,在使用非空扫描字段插入“扫描设置”之前,我将获取已经存在的记录,以查看刚检索的记录和要插入的记录之间是否存在重复的文档的ObjectId。 换句话说,我将通过Python而不是MogoneEngine / Mongodb来检查唯一性。我将需要插入2 x个扫描实例(读取并使用add_set_operator更新)+ 1个ScanSettings保存
选项C 忽略唯一性。鉴于我的模型将如何构造,我非常确定不会有重复项,或者如果有重复项,则可以忽略不计。然后在阅读时处理重复项。对于像我这样的来自关系数据库的人来说,这种解决方案很麻烦。
我是Mongo的新手,因此,我感谢您提出任何意见。谢谢。
PS:我正在使用最新的MongoEngine和免费的Mongodb。
非常感谢。
答案 0 :(得分:0)
我最终选择了选项A,因此我将模型重构为:
a)创建一个继承自Document类的Mixin类,以添加两种方法:覆盖“保存”,以便仅在唯一文档列表为空时允许保存;以及“ save_with_uniqueness”,允许在保存唯一文档时进行保存和/或更新。文档列表为空。这个想法是要强制唯一性。
b)重构扫描和ScanSettings,以便前者将“ scans”字段重新定义为对Scans的引用的ListField,后者则重新定义为继承自Document而不是Embedded Document。
c)现实是Scans和ScanSettings现在是从Mixin类继承的,因为这两个类都需要分别保证其属性“文档”和“扫描”的唯一性。因此就是Mixin类。
使用a)和b),我可以保证唯一性,并首先为它保存每个扫描实例,以后再以通常的方式添加到ScanSettings.scans中。
针对像我这样的新手的几点建议:
最后是代码。它尚未经过全面测试,但足以使主要思想适合我的需求。
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'}