在sqlalchemy中插入具有一对多关系的新记录

Posted

技术标签:

【中文标题】在sqlalchemy中插入具有一对多关系的新记录【英文标题】:Inserting new records with one-to-many relationship in sqlalchemy 【发布时间】:2013-05-02 06:00:32 【问题描述】:

我正在关注 declaring models 上关于一对多关系的 flask-sqlalchemy 教程。示例代码如下:

class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    addresses = db.relationship('Address', backref='person',
                                lazy='dynamic')

class Address(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(50))
    person_id = db.Column(db.Integer, db.ForeignKey('person.id'))

现在我想知道如何使用这种模型将新记录插入数据库。我假设我需要一个构造函数 init,但我很难理解它应该如何实现和使用。我这里的主要问题是 Person 依赖于 Address 并且 Address 对 Person 有 ForeignKey,所以它应该提前知道 Person。

请帮助我了解它应该如何执行。

提前谢谢你。

【问题讨论】:

【参考方案1】:

您不需要编写构造函数,您可以将Person 实例上的addresses 属性视为一个列表:

a = Address(email='foo@bar.com')
p = Person(name='foo')
p.addresses.append(a)

或者您可以将地址列表传递给Person 构造函数

a = Address(email='foo@bar.com')
p = Person(name='foo', addresses=[a])

在任何一种情况下,您都可以像这样访问Person 实例上的地址:

db.session.add(p)
db.session.add(a)
db.session.commit()
print(p.addresses.count()) # 1
print(p.addresses[0]) # <Address object at 0x10c098ed0>
print(p.addresses.filter_by(email='foo@bar.com').count()) # 1

【讨论】:

太棒了!现在,如果数据库中已经存在一个人,并且我们已经为他们添加了一个新地址 (p.addresses.append(a)),那么我们是否需要在 db.session.commit 之前添加db.session.add 人、地址、两者,或者两者都不是叮? 您必须从数据库中查询现有数据,例如p = db.session.query(Person).filter(Person.name == 'Name of existing Person').first(),然后执行相同的p.addresses.append(a)。您不必将它添加到会话中,因为它直接来自会话。但在所有更改完成后我仍然会db.session.commit() 这会创建一个新的Address 记录。如果我想用email='foo@bar.com' 分配Address 记录的id(主键)怎么办。我想将已经创建的地址分配给人员,但我不想先查询地址。【参考方案2】:

研究这个模型时最重要的是要理解这个模型具有一对多关系的事实,即一个人有多个地址,在我们的例子中,我们会将这些地址存储在一个列表中。

因此,带有 init 的 Person 类将如下所示。

class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    addresses = db.relationship('Address', backref='person',
                            lazy='dynamic')

    def __init__(self,id,name,addresses = tuple()):
        self.id = id
        self.name = name
        self.addresses = addresses

所以这个 Person 类将需要一个 id、一个名称和一个包含 Address 类型对象的列表。我将默认值保留为空列表。

希望对您有所帮助。 :)

【讨论】:

可变的默认参数是common gotcha,您可能需要更改示例以反映这一点:) @DazWorrall 我编辑了答案以改变它。【参考方案3】:

我在这里和其他地方收集了信息,并找到了 3 种方法。在此模型示例中(与问题相同):

class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    addresses = db.relationship('Address', backref='person',
                                lazy='dynamic')

class Address(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(50))
    person_id = db.Column(db.Integer, db.ForeignKey('person.id'))

1.

a = Address(email='foo@bar.com')
p = Person(name='foo', addresses=[a])

2.

p = Person(name='foo')
a = Address(email='foo@bar.com', person_id=p.id)

3.

a = Address(email='foo@bar.com')
p = Person(name='foo')
p.addresses.append(a)

【讨论】:

【参考方案4】:

除了所有之前的答案,与uselist=False 建立一对一关系,例如:

class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    address = db.relationship('Address', backref='person',
                                lazy=True, uselist=False)

class Address(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(50))
    person_id = db.Column(db.Integer, db.ForeignKey('person.id'))

只有下一种方法有助于插入记录:

p = Person(name='foo')
a = Address(email='foo@bar.com', person=p)  # Adding created person directly to Address's backref

【讨论】:

以上是关于在sqlalchemy中插入具有一对多关系的新记录的主要内容,如果未能解决你的问题,请参考以下文章

嵌套的一对多关系 sqlalchemy 过滤

具有一对一关系的 SQLAlchemy 聚合

SQLAlchemy 一对多关系澄清

核心数据需要时间来插入具有获取实体的记录并设置为关系

Sequelize.js 插入具有一对多关系的模型

具有多个多对多关系的 SQLAlchemy