SQLAlchemy ORM not working with composite foreign keys

前端 未结 1 1617
名媛妹妹
名媛妹妹 2021-01-23 10:15

I\'m trying to build an example with several related models, like the following. We have a model B with a 1:n relation with a model C; then we have a model A with a n:1 relation

相关标签:
1条回答
  • 2021-01-23 10:39

    The issue here is that you have to declare the ForeignKeyConstraint in __table_args__, not in the body of the class.

    In other words, the following code will NOT apply the foreign key constraint to the child table ...

    from sqlalchemy import (
        create_engine,
        Column,
        Integer,
        text,
        ForeignKeyConstraint,
        String,
    )
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import relationship
    
    connection_uri = (
        r"mssql+pyodbc://@.\SQLEXPRESS/myDb?driver=ODBC+Driver+17+for+SQL+Server"
    )
    engine = create_engine(connection_uri, echo=True,)
    
    with engine.connect() as conn:
        for tb_name in ["tbl_child", "tbl_parent"]:
            conn.execute(text(f"DROP TABLE IF EXISTS [{tb_name}]"))
    
    Base = declarative_base()
    
    
    class Parent(Base):
        __tablename__ = "tbl_parent"
        id1 = Column(Integer, primary_key=True)
        id2 = Column(Integer, primary_key=True)
        parent_name = Column(String(50))
        children = relationship("Child", back_populates="parent")
    
        def __repr__(self):
            return f"<Parent(id1={self.id1}, id2={self.id2}), parent_name='{self.parent_name}'>"
    
    
    class Child(Base):
        __tablename__ = "tbl_child"
        id = Column(Integer, primary_key=True, autoincrement=False)
        child_name = Column(String(50))
        parent_id1 = Column(Integer)
        parent_id2 = Column(Integer)
        ForeignKeyConstraint(
            ["parent_id1", "parent_id2"], ["tbl_parent.id1", "tbl_parent.id2"]
        )
        parent = relationship(
            "Parent",
            foreign_keys="[Child.parent_id1, Child.parent_id2]",
            back_populates="children",
        )
    
        def __repr__(self):
            return f"<Child(id={self.id}, child_name={self.child_name})>"
    
    
    Base.metadata.create_all(engine)
    
    """ console output:
    2020-04-30 06:57:13,899 INFO sqlalchemy.engine.Engine 
    CREATE TABLE tbl_parent (
        id1 INTEGER NOT NULL, 
        id2 INTEGER NOT NULL, 
        parent_name VARCHAR(50), 
        PRIMARY KEY (id1, id2)
    )
    
    
    2020-04-30 06:57:13,899 INFO sqlalchemy.engine.Engine ()
    2020-04-30 06:57:13,900 INFO sqlalchemy.engine.Engine COMMIT
    2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine 
    CREATE TABLE tbl_child (
        id INTEGER NOT NULL, 
        child_name VARCHAR(50), 
        parent_id1 INTEGER, 
        parent_id2 INTEGER, 
        PRIMARY KEY (id)
    )
    
    
    2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine ()
    2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine COMMIT
    """
    

    ... but this will ...

    from sqlalchemy import (
        create_engine,
        Column,
        Integer,
        text,
        ForeignKeyConstraint,
        String,
    )
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import relationship
    
    connection_uri = (
        r"mssql+pyodbc://@.\SQLEXPRESS/myDb?driver=ODBC+Driver+17+for+SQL+Server"
    )
    # connection_uri = "sqlite:///:memory:"
    engine = create_engine(connection_uri, echo=True,)
    
    with engine.connect() as conn:
        for tb_name in ["tbl_child", "tbl_parent"]:
            conn.execute(text(f"DROP TABLE IF EXISTS [{tb_name}]"))
    
    Base = declarative_base()
    
    
    class Parent(Base):
        __tablename__ = "tbl_parent"
        id1 = Column(Integer, primary_key=True)
        id2 = Column(Integer, primary_key=True)
        parent_name = Column(String(50))
        children = relationship("Child", back_populates="parent")
    
        def __repr__(self):
            return f"<Parent(id1={self.id1}, id2={self.id2}), parent_name='{self.parent_name}'>"
    
    
    class Child(Base):
        __tablename__ = "tbl_child"
        __table_args__ = (
            ForeignKeyConstraint(
                ["parent_id1", "parent_id2"], ["tbl_parent.id1", "tbl_parent.id2"]
            ),
        )
        id = Column(Integer, primary_key=True, autoincrement=False)
        child_name = Column(String(50))
        parent_id1 = Column(Integer)
        parent_id2 = Column(Integer)
    
        parent = relationship(
            "Parent",
            foreign_keys="[Child.parent_id1, Child.parent_id2]",
            back_populates="children",
        )
    
        def __repr__(self):
            return f"<Child(id={self.id}, child_name={self.child_name})>"
    
    
    Base.metadata.create_all(engine)
    
    """ console output:
    CREATE TABLE tbl_parent (
        id1 INTEGER NOT NULL, 
        id2 INTEGER NOT NULL, 
        parent_name VARCHAR(50) NULL, 
        PRIMARY KEY (id1, id2)
    )
    
    
    2020-04-30 07:52:43,771 INFO sqlalchemy.engine.Engine ()
    2020-04-30 07:52:43,776 INFO sqlalchemy.engine.Engine COMMIT
    2020-04-30 07:52:43,778 INFO sqlalchemy.engine.Engine 
    CREATE TABLE tbl_child (
        id INTEGER NOT NULL, 
        child_name VARCHAR(50) NULL, 
        parent_id1 INTEGER NULL, 
        parent_id2 INTEGER NULL, 
        PRIMARY KEY (id), 
        FOREIGN KEY(parent_id1, parent_id2) REFERENCES tbl_parent (id1, id2)
    )
    
    
    2020-04-30 07:52:43,778 INFO sqlalchemy.engine.Engine ()
    2020-04-30 07:52:43,802 INFO sqlalchemy.engine.Engine COMMIT
    """
    

    Reference:

    https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/table_config.html#table-configuration

    0 讨论(0)
提交回复
热议问题