2

たとえば、次のようなモデルが必要です。

  • 作品 - 文学作品など。
  • 労働者 - 作曲家、翻訳者など、仕事に貢献する人。

その'type'ため、作業者を分業で区別するためのフィールドが必要です。SQLAlchemy のドキュメントとして、このケースは次のような関連オブジェクトから恩恵を受けることができます:

class Work(base):
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    description = Column(Text)

class Worker(base):
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    description = Column(Text)

class Assignment(base):
    work_id = Column(Integer, Foreignkey('work.id'), primary_key=True)
    worker_id = Column(Integer, Foreignkey('worker.id'), primary_key=True)
    type = Column(SmallInteger, nullable=True)

それにもかかわらず、各オブジェクトが区別のために異なる属性を介して対応するものを取得および変更できるように実装するために、関係を構築するためにbackrefおよび代替結合条件を利用する方法。例えば:WorkWorker

work = session.query(Work).get(1)
work.name
>>> 'A Dream of The Red Mansions'
work.composers
>>> [<Worker('Xueqin Cao')>]
work.translators
>>> [<Worker('Xianyi Yang')>, <Worker('Naidie Dai')>]

逆に:

worker = session.query(Worker).get(1)
worker.name
>>> 'Xueqin Cao'
worker.composed
>>> [<Work('A Dream of The Red Mansions')>]
worker.translated
>>> []

secondaryjoin指定せずに直接追加することsecondaryは現実的ではないようですが、SQLAlchemy のドキュメントには次のように記載されています。

関連付けオブジェクト パターンを使用する場合、relationship() にオプション viewonly=True が含まれていない限り、関連付けマップ テーブルを他の relationship() の 2 番目の引数として使用しないことをお勧めします。そうしないと、関連する属性と関連するオブジェクトで同様の状態が検出された場合、SQLAlchemy は同じテーブルで冗長な INSERT ステートメントと DELETE ステートメントを発行しようとする可能性があります。

では、これらの関係をエレガントかつ簡単に構築する方法はありますか?

4

1 に答える 1

3

ここに行くには3つの一般的な方法があります。

1つは、「タイプ」を区別せずに「仕事」/「労働者」を設定する「バニラ」設定を行い、relationship()「作曲家」、「作曲」、「翻訳者」、「翻訳」に使用します。セカンダリ」をAssignment.__table__カスタム結合条件と一緒に、およびviewonly=True. したがって、バニラのプロパティのみを介して書き込みを行います。ここでの欠点は、「バニラ」コレクションと「特定の」コレクションの間に即時の同期がないことです。

もう1つは、「バニラ」セットアップと同じですが、プレーンなPython記述子を使用して、メモリ内の「作曲者」、「構成済み」、「翻訳者」、「翻訳済み」ビュー、つまり[obj.worker for obj in self.workers if obj.type == 'composer']. これが最も簡単な方法です。「バニラ」コレクションに入れるものはすべて「フィルター処理された」コレクションに表示され、SQL は単純であり、実行される SELECT ステートメントは少なくなります (ワーカー/ワークごとに N ではなく、ワーカー/ワークごとに 1 つ)。

最後に、一次結合と後方参照を使用した、あなたが求めているものに最も近いアプローチですが、関連付けオブジェクトでは、後方参照は作業/割り当てと割り当て/ワーカーの間にありますが、直接作業/ワーカーの間にはありません。このアプローチは、おそらくより多くの SQL を使用して結果を取得することになりますが、最も完全であり、「型」が自動的に書き込まれるという気の利いた機能もあります。また、Assignment には外部に関連付ける簡単な方法がないため、「一方向後方参照」も使用しています (それを行う方法はありますが、面倒です)。Python 関数を使用してリレーションシップの作成を自動化すると、ボイラープレートが削減されます。ここでは、「タイプ」に文字列を使用していることに注意してください。システムに引数を追加すると、これは整数になる可能性があります。

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy

Base = declarative_base()

def _work_assignment(name):
    assign_ = relationship("Assignment",
                    primaryjoin="and_(Assignment.work_id==Work.id, "
                                    "Assignment.type=='%s')" % name,
                    back_populates="work", cascade="all, delete-orphan")
    assoc = association_proxy("%s_assign" % name, "worker",
                    creator=lambda worker: Assignment(worker=worker, type=name))
    return assoc, assign_

def _worker_assignment(name):
    assign_ = relationship("Assignment",
                    primaryjoin="and_(Assignment.worker_id==Worker.id, "
                                    "Assignment.type=='%s')" % name,
                    back_populates="worker", cascade="all, delete-orphan")
    assoc = association_proxy("%s_assign" % name, "work",
                    creator=lambda work: Assignment(work=work, type=name))
    return assoc, assign_

class Work(Base):
    __tablename__ = 'work'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    description = Column(Text)

    composers, composer_assign = _work_assignment("composer")
    translators, translator_assign = _work_assignment("translator")

class Worker(Base):
    __tablename__ = 'worker'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    description = Column(Text)

    composed, composer_assign = _worker_assignment("composer")
    translated, translator_assign = _worker_assignment("translator")

class Assignment(Base):
    __tablename__ = 'assignment'
    work_id = Column(Integer, ForeignKey('work.id'), primary_key=True)
    worker_id = Column(Integer, ForeignKey('worker.id'), primary_key=True)
    type = Column(String, nullable=False)

    worker = relationship("Worker")
    work = relationship("Work")

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)


session = Session(e)

ww1, ww2, ww3 = Worker(name='Xueqin Cao'), Worker(name='Xianyi Yang'), Worker(name='Naidie Dai')

w1 = Work(name='A Dream of The Red Mansions')
w1.composers.append(ww1)
w1.translators.extend([ww2, ww3])

session.add(w1)
session.commit()

work = session.query(Work).get(1)
assert work.name == 'A Dream of The Red Mansions'
assert work.composers == [ww1]
assert work.translators == [ww2, ww3]

worker = session.query(Worker).get(ww1.id)
assert worker.name == 'Xueqin Cao'
assert worker.composed == [work]
assert worker.translated == []

worker.composed[:] = []

# either do this...
session.expire(work, ['composer_assign'])

# or this....basically need composer_assign to reload
# session.commit()

assert work.composers == []
于 2013-03-08T21:24:31.247 に答える