【问题标题】:SqlAlchemy: Convert inherited type from one to anotherSqlAlchemy:将继承的类型从一种转换为另一种
【发布时间】:2014-05-04 22:27:07
【问题描述】:

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

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 中找到它。请注意,这是一个人为的例子。

【问题讨论】:

  • 我认为与其编写纯 SQL,不如创建一个 Manager 实例并复制任何相关属性,然后删除原始 Worker。但这可能会很困难,具体取决于涉及多少外键等。

标签: python sqlalchemy flask-sqlalchemy


【解决方案1】:

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

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>}
    

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

【讨论】:

  • 这是一个经过深思熟虑的描述性答案。谢谢。
【解决方案2】:

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

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

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

【讨论】:

  • 总的来说这是一个很好的建议。但是,虽然我的示例是人为设计的,但我仍然需要知道如何执行此操作,因为我的对象与某些身份列非常不相关。
猜你喜欢
  • 2017-04-16
  • 1970-01-01
  • 2011-09-24
  • 1970-01-01
  • 2012-03-28
  • 2020-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多