在经典映射风格中使用多态类

Bru*_*ira 5 sqlalchemy

文档中关于加入继承示例使用了声明性映射。我正在尝试将其调整为使用“经典映射”,但无法正常工作。

我已经阅读并使用了https://docs.sqlalchemy.org/en/14/orm/inheritance.html 中的文档作为指南。

我有一些简单的类使用attrs

class Person:
    pass


@attr.s(auto_attribs=True)
class Manager(Person):
    name: str
    data: str


@attr.s(auto_attribs=True)
class Engineer(Person):
    name: str
    info: int


@attr.s(auto_attribs=True)
class Company:
    people: list[Person]
Run Code Online (Sandbox Code Playgroud)

我声明映射和表如下:

persons_table = Table(
    "person",
    metadata,
    Column("id", Integer, primary_key=True),
)

managers_table = Table(
    "manager",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("data", String(50)),
)

engineers_table = Table(
    "engineer",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("info", Integer),
)


company_table = Table(
    "company",
    metadata,
    Column("id", Integer, primary_key=True),
)

pjoin = polymorphic_union(
    {"person": persons_table, "manager": managers_table, "engineer": engineers_table},
    "type",
    "pjoin",
)


company_2_people_table = Table(
    "company_2_people",
    metadata,
    Column("id", Integer, primary_key=True, autoincrement=True),
    Column("company_id", ForeignKey("company.id")),
    Column("person_id", ForeignKey("person.id")),
)


person_mapper = mapper(
    Person,
    pjoin,
    with_polymorphic=("*", pjoin),
    polymorphic_on=pjoin.c.type,
)
manager_mapper = mapper(
    Manager,
    managers_table,
    inherits=person_mapper,
    concrete=True,
    polymorphic_identity="manager",
)
engineer_mapper = mapper(
    Engineer,
    engineers_table,
    inherits=person_mapper,
    concrete=True,
    polymorphic_identity="engineer",
)

company_mapper = mapper(
    Company,
    company_table,
    properties={
        "people": relationship(
            person_mapper,
            secondary=company_2_people_table,
            collection_class=list,
        ),
    },
)
Run Code Online (Sandbox Code Playgroud)

一个简单的测试:

fn = Path(__file__).with_suffix(".db")
fn.unlink(missing_ok=True)
engine = create_engine(f"sqlite:///{fn}", echo=True)
metadata.create_all(engine)

Session = sessionmaker(bind=engine)
with Session() as session:
    m1 = Manager(name="Manager 1", data="Manager Data")
    m2 = Manager(name="Manager 2", data="Manager Data")
    e1 = Engineer(name="Eng", info=10)

    company = Company([m1, e1, m2])
    session.add(company)
    session.commit()

with Session() as session:
    print(session.query(Company).get(1))
Run Code Online (Sandbox Code Playgroud)

这运行,但是我得到这个输出:

Company(people=[Engineer(name='Eng', info=10), Manager(name='Manager 1', data='Manager Data'), Manager(name='Manager 2', data='Manager Data')])
Run Code Online (Sandbox Code Playgroud)

请注意,虽然实例是正确的,但顺序不是:它应该是Manager, Engineer, Manager

将我的数据库文件与从文档中的示例生成的文件进行比较:

  • 在文档的person表中,该表包含所有人,以及一type列包含该人的类型。
  • 在我的person表中,表是空的,只包含一id列(没有type)。

我已经调试了示例生成的运行时类,并尝试模仿那里的结构(例如,显式传递 internal _polymorphic_map,但无济于事)。

我也改变了主键定义ManagerEngineerColumn('id', ForeignKey("person.id"), primary_key=True),但是我得到一个异常:

sqlalchemy.orm.exc.FlushError: Instance <Engineer at 0x198e43cd280> has a NULL identity key.  If this is an auto-generated value, check that the database table allows generation of new primary key values, and that the mapped Column object is configured to expect these generated values.  Ensure also that this flush() is not occurring at an inappropriate time, such as within a load() event.
Run Code Online (Sandbox Code Playgroud)

任何其他可能为我指明正确方向的建议或提示?

谢谢。

我已经在https://gist.github.com/nicoddemus/26de7bbcdfa9ed4b14fcfdde72b1d63f 上发布了完整的源代码。

Bru*_*ira 6

更仔细地阅读示例后,我发现我做错了:我将联合继承的概念与具体继承混合在一起。

我想加入继承,所以:

  1. 每个表子类都需要将其主键定义为基表的外键:
engineers_table = Table(
    "engineer",
    metadata,
    Column('id', ForeignKey("person.id"), primary_key=True),
    Column("name", String(50)),
    Column("info", Integer),
)
Run Code Online (Sandbox Code Playgroud)
  1. 基本映射器需要指定使用哪一列作为分母:
person_mapper = mapper_registry.map_imperatively(
    Person,
    persons_table,
    polymorphic_identity="person",
    polymorphic_on=persons_table.c.type,
)
Run Code Online (Sandbox Code Playgroud)
  1. 每个子类还需要指定它们的多态身份:
manager_mapper = mapper_registry.map_imperatively(
    Manager,
    managers_table,
    inherits=person_mapper,
    polymorphic_identity="manager",
)
Run Code Online (Sandbox Code Playgroud)

就是这样,SQLA 负责其余的工作。我已经用完整的和现在工作的代码更新了 Gist 链接,以防它可能对其他人有所帮助。

https://gist.github.com/nicoddemus/26de7bbcdfa9ed4b14fcfdde72b1d63f