Эффективно обновлять ListField of Documents в списке внедренного документа тогда и только тогда, когда соблюдается уникальность в самом внутреннем списке - PullRequest
0 голосов
/ 23 октября 2018

Я только что натолкнулся на сценарий, который я не знаю, как решить с существующей структурой моих документов.Как показано ниже, я, очевидно, могу решить эту проблему с помощью некоторого рефакторинга, но Мне интересно, как это можно решить наиболее эффективно и с учетом той же структуры .

Пожалуйста, обратите внимание, что этот вопросотличается от Как сделать атомарное обновление для EmbeddedDocument в ListField в 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

Я выполняю рефакторинг своей "схемы" и делаю коллекцию сканов наследованной из документа, а не встроенного документа, и изменяю поле сканирования ScanSettings на ListField of ReferenceFields для сканирования документов.Тогда это легко, так как мне просто нужно сначала сохранить сканы, используя «Updates» с оператором «add_to_set» и опцией «upsert = True».Затем, как только операция будет одобрена, сохраните ScanSettings.Мне понадобится количество экземпляров сканирования, чтобы вставить + 1 запрос.

Опция B Я сохраняю ту же "схему", но каким-то образом генерирую уникальные идентификаторы для внедренного документа Сканирования.Затем перед любой вставкой параметров сканирования с непустым полем сканирования я извлекаю уже существующие записи, чтобы увидеть, есть ли идентификаторы объекта дублированного документа среди только что извлеченных записей и тех, которые нужно вставить.Другими словами, я бы проверял уникальность через Python, а не через MogoneEngine / Mongodb.Мне понадобится 2 x количество экземпляров сканирования для вставки (чтение + обновление с помощью add_set_operator) + 1 ScanSettings save

Опция C Игнорировать уникальность.Учитывая то, как будет структурирована моя модель, я почти уверен, что дубликатов не будет или, если они вообще есть, она будет незначительной.Затем разберитесь с дубликатами во время чтения.Для тех, кто похож на меня, пришедших из реляционных баз данных, это решение выглядит сложным.

Я новичок в Монго, поэтому я благодарен за любые комментарии.Спасибо.

PS: Я использую последнюю версию MongoEngine и бесплатную версию Mongodb.

Заранее большое спасибо.

1 Ответ

0 голосов
/ 24 октября 2018

Наконец-то я выбрал вариант A, поэтому я реорганизовал мою модель:

a) Создайте класс Mixin, который наследуется от класса Document, чтобы добавить два метода: переопределить 'save', чтобы он позволял сохранять только тогда, когдасписок уникальных документов пуст и «save_with_uniqueness», который позволяет сохранять и / или обновлять, когда список документов пуст.Идея состоит в том, чтобы обеспечить уникальность.

b) Рефакторинг Scans и ScanSettings означает, что первый переопределяет поле 'scan' как ListField со ссылками на Scan, а второй так, что он наследуется от Document, а не от Embedded Document.

в) Реальность такова, что Scans и ScanSettings теперь наследуются от класса Mixin, так как оба класса должны гарантировать уникальность как для своих атрибутов «документы», так и «сканы», соответственно.Следовательно, класс Mixin.

С помощью а) и б) я могу гарантировать уникальность и сохранять сначала каждый экземпляр сканирования, чтобы впоследствии его можно было добавить в ScanSettings.scans обычным способом.

AНесколько моментов для таких новичков, как я:

  1. Смотри, что я использую наследование.Чтобы это работало, вам нужно также добавить атрибут в мета-словарь, чтобы разрешить наследование, как показано на модели ниже.
  2. В моем случае я хотел иметь сканы и ScanSettings в разных коллекциях, поэтому мне пришлось их создавать 'abstract ', как показано в мета-словаре класса Mixin.
  3. Для save_with_uniqueness я использовал upsert = True, чтобы, если запись может быть создана, если ее не существует.Идея состоит в том, чтобы использовать «save_with_uniqueness» так же, как «сохранять, создавать или обновлять документ, если он существует или нет.
  4. Я также использовал флаг« full_result », так как мне нужно вернуть ObjectId последней записивставлен.
  5. Document._fields - это словарь, содержащий поля, составляющие этот документ.Я действительно хотел создать универсальный метод save_with_uniqueness, поэтому мне не нужно было вручную вводить поля в документе или просто дублировать ненужный код - отсюда и 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'}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...