SQLAlchemy 命令式映射无法使用另一个实体的外键添加实体

Posted

技术标签:

【中文标题】SQLAlchemy 命令式映射无法使用另一个实体的外键添加实体【英文标题】:SQLAlchemy imperative mapping can't add entity using foreign key for another entity 【发布时间】:2021-10-31 21:12:31 【问题描述】:

我在尝试使用外键添加新实体时遇到了一些 SQLAlchemy 命令式映射问题,例如。

如果我将映射类定义为:

@attr.s
class Foreign:
    id = attr.ib(type=int, validator=optional(instance_of(int)), default=None)
    name = attr.ib(type=str, validator=instance_of(str), default=None)


@attr.s
class Primary:
    id = attr.ib(type=int, validator=optional(instance_of(int)), default=None)
    foreign_id = attr.ib(type=int, validator=optional(instance_of(int)), default=None)
    foreign = attr.ib(type=Foreign, validator=optional(instance_of(Foreign)), default=None)

当添加新实体时:

session.add(Primary(foreign_id=foreign_id))
session.commit()

它失败了,因为它用 None 替换了 foreign_id,因为我猜它期望填充外部属性。

这些是我的桌子:

mapper_registry = registry()

primary = Table(
    "primary",
    metadata,
    Column("id", Integer, primary_key=True, autoincrement=True),
    Column("foreign_id", Integer, ForeignKey("foreign.id"), nullable=False),
)


foreign = Table(
    "foreign",
    metadata,
    Column("id", Integer, primary_key=True, autoincrement=True),
    Column("name", String(20), nullable=False),
)


def configure_mappers():
    mapper_registry.map_imperatively(Foreign, foreign)

def configure_mappers():
    mapper_registry.map_imperatively(
        Primary,
        primary,
        properties=
            "foreign": relationship(Foreign),
        ,
    )

但是,如果我从映射类中删除 foreign 属性,它就可以完美地工作。

如果我的映射类具有到外键和外键的映射,我是否可以使用外键添加新实体?

提前致谢

【问题讨论】:

【参考方案1】:

来自attrs documentation:

尝试以简洁方便的方式设计您的课程 使用 - 不基于您的数据库格式。数据库格式可以 随时更改,你会被一个糟糕的类设计困住,这很难 改变。将函数和类方法作为过滤器 现实以及最适合您的工作。

如果您绝对需要从 Python 代码访问 id 和/或从外部 id 和对象实例化您的 Primary 对象,您可以检查类实例方法并编写两个类方法来实例化 Primary:

用外来对象实例化 Primary, 另一个使用外部对象 id 作为外键来实例化 Primary。

另类设计

据我了解,ORM 的价值(尤其是其命令式映射风格)是能够表达您的代码,而无需处理诸如 id 和外键之类的数据库概念。

此外,能够实例化具有离散 id 的 Primary 或 Foreign 对象与将其用作主键这一事实相矛盾:在 Python 代码中没有强制执行具有唯一键的约束。

命令式映射的优势在于将域对象与其数据库实现完全解耦,因此您可以让 SQLAlchemy 在幕后处理 id。

model.py

​​>
import attr
from attr.validators import instance_of
from attr.validators import optional


@attr.s
class Foreign:
    name = attr.ib(type=str, validator=instance_of(str), default=None)


@attr.s
class Primary:
    name = attr.ib(type=str, validator=instance_of(str), default=None)
    foreign = attr.ib(type=Foreign, validator=optional(instance_of(Foreign)), default=None)

orm.py

​​>
import model
from sqlalchemy.orm import registry
from sqlalchemy.orm import relationship
from sqlalchemy.schema import MetaData
from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy import Table


metadata = MetaData()
mapper_registry = registry()

table_primary = Table(
    "primary",
    metadata,
    Column("id", Integer, primary_key=True, autoincrement=True),
    Column("foreign_id", Integer, ForeignKey("foreign.id"), nullable=False),
)


table_foreign = Table(
    "foreign",
    metadata,
    Column("id", Integer, primary_key=True, autoincrement=True),
    Column("name", String(20), nullable=False),
)


def configure_mappers():
    mapper_registry.map_imperatively(model.Foreign, table_foreign)

    mapper_registry.map_imperatively(
        model.Primary,
        table_primary,
        properties=
            "foreign": relationship(model.Foreign),
        ,
    )

用法

import orm
import model
import sqlalchemy

engine = sqlalchemy.create_engine("sqlite:///my_db.db")

orm.metadata.create_all(engine)
orm.configure_mappers()
Session = sqlalchemy.orm.sessionmaker(bind=engine)
session = Session()


foreign_1 = model.Foreign(name="first foreign")
foreign_2 = model.Foreign(name="second foreign")
foreign_3 = model.Foreign(name="third foreign")
primary_a = model.Primary(name="first primary", foreign=foreign_3)
primary_b = model.Primary(name="second primary", foreign=foreign_2)
session.add_all([foreign_1, primary_a, primary_b])
session.commit()

【讨论】:

以上是关于SQLAlchemy 命令式映射无法使用另一个实体的外键添加实体的主要内容,如果未能解决你的问题,请参考以下文章

JPA ManyToMany 映射问题(无法将同一实体映射到另一个)

在身份映射之外强制使用 sqlalchemy ORM get()

SQLAlchemy 一个映射类中的多个外键到同一个主键

SQLAlchemy 如何使用声明性映射一对一关系的单列

如何进行 Hibernate XML 映射,一对多使用 1 PK 映射到另一个具有复合键的实体

如何将一个类映射到sqlalchemy orm中的多个数据库