SqlAlchemy:将继承的类型从一个转换为另一个

时间:2013-04-18 03:36:18

标签: python sqlalchemy flask-sqlalchemy

假设我在同一个数据库表(单表继承)上有两种不同的类型:

class Employee(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    name = db.Column(db.String, nullable = False)
    discriminator = db.Column('type', String)
    __mapper_args__ = {'polymorphic_on': discriminator}

class Manager(Employee):
    __mapper_args__ = {'polymorphic_identity': 'manager'}
    division = db.Column(db.String, nullable = False)
    role = db.Column(db.String, nullable = False)

class Worker(Employee):
    __mapper_args__ = {'polymorphic_identity': 'worker'}
    title = db.Column(db.String, nullable = False)

(是的,我正在使用Flask-SqlAlchemy而不是普通的vanilla)现在我该如何将一个声明性模型转换为另一个声明模型。也就是说,如果“工人”被提升为“经理人”怎么办?我怎么做?我是否必须编写原始SQL来执行此操作?

很抱歉,如果以前曾经问过这个问题,但是我无法从谷歌找到它。请注意,这是一个人为的例子。

2 个答案:

答案 0 :(得分:10)

它是kludgy,它会引发警告,但你可以通过设置属性来强制修改鉴别器列:

john_smith = session.query(Employee).filter_by(name='john smith').one()
john_smith.discriminator = 'manager'
session.commit()

这将导致类似

的警告
SAWarning: Flushing object <Worker at 0xdeadbeef> with incompatible polymorphic
identity 'manager'; the object may not refresh and/or load correctly
    mapper._validate_polymorphic_identity(mapper, state, dict_)

你可以忽略它,只要你解决它会导致的问题。 最安全的事情是在提交后立即关闭会话(session.close())或从中删除所有内容(session.expunge_all())。

如果必须,只需从会话中删除John(session.expunge(john_smith)),就可以单独修复John的对象问题。你必须要小心;对john_smith的任何剩余引用都将保留该对象,但幸运的是,他将与session分离,并且您不会被允许对它们做任何事情。


我也尝试了其他明显的选择。两者都没有奏效,但两者都说明了SQLAlchemy的Session对象存储以及如何存储:

  1. session.refresh(john_smith)失败,

    InvalidRequestError: Could not refresh instance '<Worker at 0xdeadbeef>'
    

    那是因为SQLAlchemy在数据库中查询Worker(不是Employee)并且找不到John Smith的名字,因为数据库现在知道John因为他的type列中有新的价值。

  2. session.expire(john_smith)成功但未能将John更新为新课程,任何后续访问都将导致

    ObjectDeletedError: Instance '<Worker at 0xdeadbeef>' has been deleted, or
    its row is otherwise not present.
    

    SQLAlchemy仍然认为John是Worker,并且它会尝试将其作为Worker进行查询。那是因为他仍然坚持session.identity_map,看起来像这样:

    {(saexample2.Employee, (1,)): <saexample2.Worker at 0xdeadbeef>}
    

    所以有John,明确列为Worker对象。当您从会话中expunge() John时,字典中的此条目将被清除。当你expire()他时,他的所有映射属性都被标记为陈旧,但他仍然存在于字典中。

答案 1 :(得分:1)

我建议您重新设计对象模型。当一个对象的工作方式与另一个对象的属性一样好时,对象模型将从重新思考中受益的标志。在这种情况下,Worker.title同样可以是“经理”。

另外,Manager.division作为自己的对象分区效果更好。尤其是因为一个部门可以想象与工人有一对多的关系。

可能是一个Division对象,其中ForeignKey经理指向Employee个对象。 Employee对象将具有title属性;在Employee.__init__()中,您可以手动检查员工是否是任何部门的经理,然后将Employee.title设置为“经理”来自__init__()