SQLAlchemy class 继承现有实例

SQLAlchemy class inheritance on an existing instance

我正在使用 SQLAlchemy 的连接 table 继承模式,但使用确切的模式 in their docs,我无法弄清楚如何将 subclass 应用到现有实例的 parent class.

在他们的例子中:

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'employee',
        'polymorphic_on':type
    }

class Engineer(Employee):
    __tablename__ = 'engineer'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    engineer_name = Column(String(30))

    __mapper_args__ = {
        'polymorphic_identity':'engineer',
    }

class Manager(Employee):
    __tablename__ = 'manager'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    manager_name = Column(String(30))

    __mapper_args__ = {
        'polymorphic_identity':'manager',
    }

...假设我已经有一名既不是工程师也不是经理的员工,但后来他们得到了晋升。做这样的事情...

employee = Employee.query.get(id)
m = Manager()
m.employee = employee
m = session.merge(employee)

...导致此错误...

FlushError: New instance <Manager at 0x7f6bf5a35490> with identity key (<class 'models.Employee'>, conflicts with persistent instance <Employee at 0x7f6bf590d210>

那么,我将如何提升某人?

使用单独的 tables(在 SQLAlchemy 中映射到不同的 classes)以完全代表不同角色中的同一实体介于非常可疑和彻底的反模式之间——正是因为你 运行 遇到的问题:实体的角色可以 改变 (在这里,通过提升) , 但将 SQL table / SQLAlchemy class 表示同一实体 运行 完全更改为您刚刚遇到的那种问题。

我建议更改您的数据模型——例如,为所有员工添加一个字段以确定他们是否是经理,并完全删除 Manager table。如果您坚持要保留此数据模型,那么晋升就会变得相当麻烦...:[=​​11=]

  1. 复制内存中正在晋升的员工身份的所有数据

  2. 从数据库中删除所述实体

  3. 只是现在,创建manager实体,把你保存的sub(1)的数据复制进去,然后存入数据库

我无法想象让现在使用的架构值得这些复杂的事情...

免责声明: 您应该保留@AlexMartelli 的选择答案。因为他是对的,只是因为......嗯......他是 THE Alex Martelli,拥有自己的维基百科页面和所有内容('nuf said)但是在看到他的答案之前我正在研究我的答案,无论如何我都会 post 它。如果有的话,就像一个小例子,它可能会阐明 SqlAlchemy 如何处理 Joined Table 继承。

当您查询 (Manager) 时,SqlAlchemy 所做的是转到 manager table,获取所有属性,还 JOINing "parent" table (employee) 通过 idForeignKey 并将其余的带入您的实例,因此您可以 "trick" SqlAlchemy 通过改变关系 "manually" (引擎的 echo=True 参数对于查看 SqlAlchemy 的内容非常有用正在做)

恕我直言,我在下面复制的是 非常 气馁,我永远不会把这样的东西投入实际生产,但是......我真的很喜欢 SqlAlchemy,而且我喜欢与任何想听的人分享我对它的能力知之甚少(对于那些不想听的人,我追逐他们并向他们大声疾呼:-D

好了(内嵌一些评论):

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.schema import CreateSchema
from sqlalchemy import event

Base = declarative_base()

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'employee',
        'polymorphic_on':type
    }

class Engineer(Employee):
    __tablename__ = 'engineer'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    engineer_name = Column(String(30))

    __mapper_args__ = {
        'polymorphic_identity':'engineer',
    }

class Manager(Employee):
    __tablename__ = 'manager'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    manager_name = Column(String(30))

    __mapper_args__ = {
        'polymorphic_identity':'manager',
    }

if __name__ == '__main__':
    engine = create_engine("mysql://root:password@localhost/tests?charset=utf8",
                           echo=True)
    Base.metadata.create_all(engine)
    Session = sessionmaker()
    Session.configure(bind=engine)
    session = Session()
    employee = Employee(name="Pepe")
    manager = Manager(name="Foobar", manager_name="Mr. Baz")
    session.add_all([employee, manager])
    session.commit()
    """ 
    table tests.employee looks like:
    |  id |   name   |    type    |
    |  1  |   Pepe   |  employee  |
    |  2  |   Foobar |   manager  |

    table tests.manager looks like:
    |  id |   manager_name   |
    |  2  |     Mr. Baz      |

    """
    to_promote = session.query(Employee).filter_by(name="Pepe").first()
    managers = [manager.name for manager in session.query(Manager).all()]
    session.close()

    print ("As of now (point 1), got %s managers: %s"
           % (len(managers), managers))
    print "Employee to_promote: %s" % to_promote.name

    connection = engine.connect()
    connection.execute(
        "START TRANSACTION;"
        "UPDATE employee SET employee.type='manager' WHERE employee.id={0};"
        "INSERT INTO manager (id, manager_name) VALUES ({0}, 'New Pepe');"
        "COMMIT;".format(to_promote.id)
    )
    connection.close()

    """ 
    table tests.employee looks like:
    |  id |   name   |    type    |
    |  1  |   Pepe   |   manager  |
    |  2  |   Foobar |   manager  |

    table tests.manager looks like:
    |  id |   manager_name   |
    |  1  |    New Pepe      |
    |  2  |     Mr. Baz      |

    """
    session = Session()
    managers = [manager.name for manager in session.query(Manager).all()]
    print ("As of now (point 2), got %s managers: %s"
           % (len(managers), managers))

这输出:

2015-03-21 13:10:41,787 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2015-03-21 13:10:41,788 INFO sqlalchemy.engine.base.Engine INSERT INTO employee (name, type) VALUES (%s, %s)
2015-03-21 13:10:41,788 INFO sqlalchemy.engine.base.Engine ('Pepe', 'employee')
2015-03-21 13:10:41,789 INFO sqlalchemy.engine.base.Engine INSERT INTO employee (name, type) VALUES (%s, %s)
2015-03-21 13:10:41,789 INFO sqlalchemy.engine.base.Engine ('Foobar', 'manager')
2015-03-21 13:10:41,790 INFO sqlalchemy.engine.base.Engine INSERT INTO manager (id, manager_name) VALUES (%s, %s)
2015-03-21 13:10:41,790 INFO sqlalchemy.engine.base.Engine (2L, 'Mr. Baz')
2015-03-21 13:10:41,791 INFO sqlalchemy.engine.base.Engine COMMIT
2015-03-21 13:10:41,827 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2015-03-21 13:10:41,828 INFO sqlalchemy.engine.base.Engine SELECT employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type 
FROM employee 
WHERE employee.name = %s 
 LIMIT %s
2015-03-21 13:10:41,828 INFO sqlalchemy.engine.base.Engine ('Pepe', 1)
2015-03-21 13:10:41,829 INFO sqlalchemy.engine.base.Engine SELECT manager.id AS manager_id, employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type, manager.manager_name AS manager_manager_name 
FROM employee INNER JOIN manager ON employee.id = manager.id
2015-03-21 13:10:41,830 INFO sqlalchemy.engine.base.Engine ()
2015-03-21 13:10:41,830 INFO sqlalchemy.engine.base.Engine ROLLBACK
As of now (point 1), got 1 managers: [u'Foobar']
Employee to_promote: Pepe
2015-03-21 13:10:41,831 INFO sqlalchemy.engine.base.Engine START TRANSACTION;UPDATE employee SET employee.type='manager' WHERE employee.id=1;INSERT INTO manager (id, manager_name) VALUES (1, 'New Pepe');COMMIT;
2015-03-21 13:10:41,831 INFO sqlalchemy.engine.base.Engine ()
2015-03-21 13:10:41,868 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2015-03-21 13:10:41,869 INFO sqlalchemy.engine.base.Engine SELECT manager.id AS manager_id, employee.id AS employee_id, employee.name AS employee_name, employee.type AS employee_type, manager.manager_name AS manager_manager_name 
FROM employee INNER JOIN manager ON employee.id = manager.id
2015-03-21 13:10:41,869 INFO sqlalchemy.engine.base.Engine ()
As of now (point 2), got 2 managers: [u'Pepe', u'Foobar']