我需要一些模型,例如:
因此,需要'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 和 alternatvie join condition 来立即构建关系,以实现每个Work
对象可以检索和修改相应的{{ 1}}(s)通过不同的归因来区分。例如:
Worker
反之亦然:
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
似乎不可行,此外,SQLAlchemy的文档指出:
当使用关联对象模式时,建议不要将关联映射表用作其他地方的关系()的辅助参数,除非该关系()包含选项viewonly = True。如果在相关属性以及关联对象上检测到类似状态,则SQLAlchemy可能会尝试在同一个表上发出冗余的INSERT和DELETE语句。
然后,有没有办法优雅而容易地建立这些关系?
答案 0 :(得分:3)
这里有三种方法可供选择。
一个是,做一个“香草”设置,你可以设置“工作”/“工人”而不区分“类型” - 然后,使用relationship()
代表“作曲家”,“编写”,“翻译“,”将“辅助”翻译为Assignment.__table__
以及自定义加入条件,以及viewonly=True
。所以你只能通过vanilla属性进行写操作。这里的缺点是“香草”和“特定”集合之间没有立即同步。
另一个是与“vanilla”设置相同,但只是使用普通的Python描述符在内存中提供“composer”,“composition”,“translator”,“translate”视图,即[obj.worker for obj in self.workers if obj.type == 'composer']
。这是最简单的方法。无论你在“vanilla”集合中放置什么,都显示在“过滤”集合中,SQL很简单,并且播放的SELECT语句较少(每个Worker / Work一个而不是每个Worker / Work的N个)。
最后,最接近你所要求的方法,主要连接和回复,但注意关联对象,backrefs在Work / Assignment和Assignment / Worker之间,但不直接在Work / Worker之间。这种方法可能会使用更多的SQL来获得结果,但是最完整,并且还具有“类型”自动编写的漂亮功能。我们也使用“单向反馈”,因为作业没有一种简单的向外关联方式(有方法可以做到,但这会很乏味)。使用Python函数自动创建关系减少了样板,并注意这里我使用的是“type”字符串,如果你向系统添加更多参数,这可以是一个整数:
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 == []