如何正确定义 SQLAlchemy backrefs 以便它们可以反映?

Posted

技术标签:

【中文标题】如何正确定义 SQLAlchemy backrefs 以便它们可以反映?【英文标题】:How to properly define SQLAlchemy backrefs so that they can be reflected? 【发布时间】:2018-10-22 16:49:19 【问题描述】:

假设我们在某个 Models.py 文件中有以下代码:

class Person(db.Model):
    __tablename__ = 'Persons'
    ID = db.Column(db.Integer, primary_key=True, nullable=False)
    Name = db.Column(db.String(255), nullable=False)

class House(db.Model):
    __tablename__ = 'Houses'
    ID = db.Column(db.Integer,primary_key=True,nullable=False)
    OwnerID = db.Column(db.Integer, nullable=False)
    TenantID = db.Column(db.Integer, nullable=False)

    __table_args__ = (
        db.ForeignKeyConstraint(
            ['OwnerID'],
            ['Persons.ID'],
        ),
        db.ForeignKeyConstraint(
            ['TenantID'],
            ['Persons.ID'],
        ),
    )

    OwnerBackref = db.relationship('Person', backref='OwnerBackref', lazy=True, foreign_keys=[OwnerID])
    TenantBackref = db.relationship('Person', backref='TenantBackref', lazy=True, foreign_keys=[TenantID])

并且我们希望使用自动映射库来反映这些模型,因此我们将这段代码放在另一个名为 Database.py 的模块中:

Base = automap_base()
engine = create_engine(DB_CONNECTION, pool_size=10, max_overflow=20)
db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
Base.prepare(engine, reflect=True)
Person = Base.classes.Persons
House = Base.classes.Houses

现在,当我在其他模块中导入 House 时,我希望能够这样做:

h = db_session.query(House).first()
print(h.OwnerBackref.Name)
print(h.TenantBackref.Name)

但是我收到一个错误,说这 2 个反向引用不存在,而是将一个名为“persons”的字段添加到我的 House 对象中,但这里的问题是它仅链接 1 个(租户或所有者)。我的意思是,如果我这样做:

print(h.persons.Name)

它只会为相应的租户或所有者打印名称,让我无法访问另一个租户的信息。 (请注意,我为 backrefs 设置的名称无处可寻)

那么,我的问题是如何使用我创建的反向引用来访问我想要的信息?我在这里做错了吗?

【问题讨论】:

【参考方案1】:

您的代码中的错误是您使用foreign_keys= 来定义表之间的关系,但是您将本地键名而不是外键名传递给函数。对于您的代码,您不能使用 foreign_keys= 来定义 House 模型中的关系,因为只有一个可能的外键 Person.ID 但可能有两个可能的本地键 House.OwnerIDHouse.TenantID。应该使用 primaryjoin= 参数来指定这一点。

class Person(db.Model):
    __tablename__ = 'Persons'
    ID = db.Column(db.Integer, primary_key=True)
    Name = db.Column(db.String(255), nullable=False)

class House(db.Model):
    __tablename__ = 'Houses'
    ID = db.Column(db.Integer,primary_key=True)
    OwnerID = db.Column(db.Integer, db.ForeignKey('Persons.ID'), nullable=False)
    TenantID = db.Column(db.Integer, db.ForeignKey('Persons.ID'), nullable=False)

    Owner = db.relationship('Person', backref='HousesOwned', primaryjoin='House.OwnerID == Person.ID')
    Tenant = db.relationship('Person', backref='HousesOccupied', primaryjoin='House.TenantID == Person.ID')

如果您将关系语句放在 Person 模型而不是 House 模型中,那么您可以使用 foreign_keys=primaryjoin= 来定义关系。下面的代码将产生与前面代码完全相同的关系。

class Person(db.Model):
    __tablename__ = 'Persons'
    ID = db.Column(db.Integer, primary_key=True)
    Name = db.Column(db.String(255), nullable=False)

    HousesOwned = db.relationship('House', backref='Owner', foreign_keys='[House.OwnerID]')
    HousesOccupied = db.relationship('House', backref='Tenant', foreign_keys='[House.TenantID]')


class House(db.Model):
    __tablename__ = 'Houses'
    ID = db.Column(db.Integer,primary_key=True)
    OwnerID = db.Column(db.Integer, db.ForeignKey('Persons.ID'), nullable=False)
    TenantID = db.Column(db.Integer, db.ForeignKey('Persons.ID'), nullable=False)

【讨论】:

我尝试了你们的建议,但不幸的是它们似乎对我不起作用。第一个的行为与我在问题中描述的相同:为我的 House 对象创建了一个名为“persons”的字段,所以如果我执行以下操作:h = models.db_session.query(models.House).first() print (h.persons.ID) 它只会打印租户的 ID 或所有者的 ID。对于第二个版本,我在我的 Peron 对象中得到一个“houses_collection”字段,但它给了我一个空列表: p = models.db_session.query(models.Person).first() print(p.houses.collection)

以上是关于如何正确定义 SQLAlchemy backrefs 以便它们可以反映?的主要内容,如果未能解决你的问题,请参考以下文章

SQLAlchemy中的 backref具体用途

SQLAlchemy学习-5.relationship之backref和back_populates参数

彻底搞懂 SQLAlchemy中的 backref

从 SQLAlchemy 查询中检索不同的 backref 实例

SQLalchemy 中的 backref 和 back_populate 的概念?

SQLAlchemy:为关系列赋值