0

ドキュメントの既存の構造で解決する方法がわからないというシナリオに出くわしました。以下に示すように、リファクタリングでこの問題を明らかに解決できますが、これが可能な限り最も効率的に解決され、同じ構造を尊重する方法に興味があります

この質問は、MongoEngine の ListField の EmbeddedDocument をアトミック更新する方法とは異なることに注意してください。

次のモデルがあるとします。

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 が同じドキュメント内のリストのすべての要素で一意性をサポートしていないことを考えると、2 つの解決策が見つかりました。

オプション A

「スキーマ」をリファクタリングし、Scans コレクションを Embedded ドキュメントではなく Document から継承させ、ScanSettings の scans フィールドを ReferenceFields の ListField から Scans ドキュメントに変更します。次に、演算子「add_to_set」とオプション「upsert = True」を使用して「Updates」を使用して最初にスキャンを保存するだけでよいので簡単です。次に、操作が承認されたら、ScanSettings を保存します。+ 1 クエリを挿入するには、スキャン インスタンスの数が必要になります。

オプション B 同じ「スキーマ」を保持しますが、どういうわけかスキャンの埋め込みドキュメントに一意の ID を生成します。次に、空でないスキャン フィールドを使用してスキャン設定を挿入する前に、既存のレコードを取得して、取得したレコードと挿入するレコードの間に重複するドキュメントの ObjectId があるかどうかを確認します。つまり、MogoneEngine/Mongodb を使用するのではなく、Python を使用して一意性をチェックします。挿入するスキャン インスタンスの数 x 2 (読み取り + add_set_operator による更新) + 1 つの ScanSettings 保存が必要です。

オプション C 一意性を無視します。私のモデルがどのように構築されるかを考えると、重複がないか、重複があるとしても無視できると確信しています。次に、読み取り時に重複を処理します。私のようにリレーショナル データベースから来ている人にとって、このソリューションは難航しているように感じます。

私はMongoの初心者なので、コメントに感謝します。ありがとう。

PS: 最新の MongoEngine と無料の Mongodb を使用しています。

よろしくお願いします。

4

1 に答える 1

0

最終的にオプション A を選択したので、モデルを次のようにリファクタリングします。

a) Document クラスから継承する Mixin クラスを作成して、2 つのメソッドを追加します。一意のドキュメントのリストが空の場合にのみ保存できるように「save」をオーバーライドし、リストが空の場合に保存および/または更新を許可する「save_with_uniqueness」をオーバーライドします。ドキュメントが空です。アイデアは、一意性を強制することです。

b) Scans と ScanSettings の両方をリファクタリングして、前者が「scans」フィールドを Scans への参照の ListField として再定義し、後者が Embedded Document ではなく Document から継承するようにします。

c) 実際には、Scans と ScanSettings は現在、Mixin クラスから継承しています。これは、両方のクラスがそれぞれ属性「ドキュメント」と「スキャン」の両方の一意性を保証する必要があるためです。したがって、Mixin クラスです。

a) と b) を使用すると、一意性を保証し、最初に各スキャン インスタンスを保存して、後で通常の方法で ScanSettings.scans に追加できます。

私のような初心者のためのいくつかのポイント:

  1. 継承を使用していることを確認してください。これを機能させるには、メタ ディクショナリに属性を追加して、以下のモデルに示すように継承を許可する必要があります。
  2. 私の場合、Scans と ScanSettings を異なるコレクションに入れたかったので、Mixin クラスのメタ ディクショナリにも示されているように、それらを「抽象化」する必要がありました。
  3. save_with_uniqueness では、upsert=True を使用して、レコードが存在しない場合にレコードを作成できるようにしました。アイデアは、「save_with_uniqueness」を「ドキュメントが存在するかどうかにかかわらず保存、作成、または更新」と同じ方法で使用することです。
  4. 挿入された最新のレコードの ObjectId を返す必要があるため、「full_result」フラグも使用しました。
  5. Document._fields は、そのドキュメントを構成するフィールドを含む辞書です。私は実際には汎用の save_with_uniqueness メソッドを作成したかったので、Document のフィールドに手動で入力したり、不要なコードを複製したりする必要はありませんでした。つまり、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'}
于 2018-10-24T09:55:24.867 に答える