Sqlalchemy 如何使用关联对象和secondaryjoin构建backref?

Sqlalchemy 如何使用关联对象和secondaryjoin构建backref?,sqlalchemy,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 = Colu

我需要一些模型,例如:

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)
  • 作品——例如文学作品
  • 工人-例如作曲家、翻译人员或对工作有贡献的类似人员
因此,需要一个
“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)
然而,如何利用backrefalternative连接条件立即建立关系,实现每个
工作
对象可以通过不同的属性检索和修改相应的
工作者
,以进行区分。例如:

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')>]
work=session.query(work.get)(1)
work.name
>>>《红楼梦》
作曲者
>>> []
翻译工作
>>> [, ]
反之亦然:

worker = session.query(Worker).get(1)
worker.name
>>> 'Xueqin Cao'
worker.composed
>>> [<Work('A Dream of The Red Mansions')>]
worker.translated
>>> []
worker=session.query(worker.get)(1)
worker.name
>>>曹雪芹
工人沉着
>>> []
工人翻译
>>> []
直接添加
secondaryjoin
而不指定
secondary
似乎不可行,此外,SQLAlchemy的文档指出:

使用关联对象模式时,建议不要将关联映射表用作其他位置relationship()的辅助参数,除非该relationship()包含选项viewonly=True。否则,如果在相关属性以及关联对象上检测到类似的状态,SQLAlchemy可能会尝试在同一个表上发出冗余的INSERT和DELETE语句


那么,有没有什么方法可以轻松优雅地建立这些关系呢?

这里有三种方法

一种是,在“普通”设置中设置“工作”/“工作者”,而不区分“类型”-然后,通过使用“次要”到
赋值,使用
relationship()
为“作曲者”、“合成者”、“翻译者”、“翻译者”使用
。\uuuu表\uuuuuu
以及自定义联接条件,以及
viewonly=True
。所以你只能通过香草属性来写。这里的一个缺点是“普通”和“特定”集合之间没有即时同步

另一个是,与“香草”设置相同,但只需使用普通Python描述符在内存中提供“composer”、“composed”、“translator”、“translated”视图,即,
[obj.worker for obj in self.workers if obj.type=='composer']
。这是最简单的方法。无论您在“香草”集合中放置什么,都会在“过滤”集合中显示出来,SQL很简单,并且使用的SELECT语句更少(每个工作者/工作一条,而不是每个工作者/工作N条)

最后,最接近您所要求的方法,使用主要联接和反向引用,但注意关联对象,反向引用在工作/分配和工作/工作者之间,而不是直接在工作/工作者之间。这种方法最终可能会使用更多的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 == []

非常感谢你。此外,它似乎正在失去惰性加载程序的好处,无法通过关联代理从一个
工作者的分配中删除
工作。如何克服或改进这种情况?我更新了它,以支持通过删除项目删除作业。但它仍然要求另一方过期/刷新以显示新数据。