16

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

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 而不是普通的香草)现在我该如何将一种声明性模型转换为另一种。也就是说,如果一个“工人”被提升为“经理”怎么办?我怎么做?我是否必须编写原始 SQL 才能做到这一点?

抱歉,如果之前有人问过这个问题,但我无法从 Google 中找到它。请注意,这是一个人为的例子。

4

2 回答 2

14

它很笨拙,并且会导致警告,但是您可以通过设置属性来蛮力修改鉴别器列:

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 在数据库中查询 a Worker(不是 a 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 是 a Worker,并尝试将他作为 a 进行查询Worker。那是因为他还在坚持session.identity_map,看起来是这样的:

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

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

于 2014-02-15T03:21:44.370 回答
1

我建议修改你的对象模型。对象模型将从重新思考中受益的一个迹象是,当一个对象与另一个对象的属性一样工作时。在这种情况下,Worker.title同样可以是“经理”。

此外,Manager.division 作为自己的对象 Division 效果更好。尤其是因为可以想象一个 Division 与 Worker 有一对多的关系。

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

于 2013-04-18T17:25:49.320 回答