【问题标题】:SQLAlchemy imperative mapping can't add entity using foreign key for another entitySQLAlchemy 命令式映射无法使用另一个实体的外键添加实体
【发布时间】: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 属性,它就可以完美地工作。

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

提前致谢

【问题讨论】:

    标签: python sqlalchemy orm foreign-keys relationship


    【解决方案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()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-18
      相关资源
      最近更新 更多