SQLAlchemy能否自动处理表间关系并添加新记录而无需手动检查主键唯一性?

6 投票
1 回答
7871 浏览
提问于 2025-04-17 12:35

我刚接触SQLAlchemy,读了一些基础文档。目前我在跟着Mike Driscoll的MediaLocker教程,并根据自己的需求进行修改和扩展。

我有三个表(贷款、人员、卡片)。卡片与贷款、人员与贷款之间都是一对多的关系,设计成这样:

from sqlalchemy import Table, Column, DateTime, Integer, ForeignKey, Unicode
from sqlalchemy.orm import backref, relation
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine("sqlite:///cardsys.db", echo=True)
DeclarativeBase = declarative_base(engine)
metadata = DeclarativeBase.metadata

class Loan(DeclarativeBase):
    """
    Loan model
    """

    __tablename__ = "loans"

    id = Column(Integer, primary_key=True)
    card_id = Column(Unicode, ForeignKey("cards.id"))
    person_id = Column(Unicode, ForeignKey("people.id"))
    date_issued = Column(DateTime)
    date_due = Column(DateTime)
    date_returned = Column(DateTime)
    issue_reason = Column(Unicode(50))
    person = relation("Person", backref="loans", cascade_backrefs=False)
    card = relation("Card", backref="loans", cascade_backrefs=False)

class Card(DeclarativeBase):
    """
    Card model
    """

    __tablename__ = "cards"

    id = Column(Unicode(50), primary_key=True)
    active = Column(Boolean)

class Person(DeclarativeBase):
    """
    Person model
    """

    __tablename__ = "people"

    id = Column(Unicode(50), primary_key=True)
    fname = Column(Unicode(50))
    sname = Column(Unicode(50))

当我尝试创建一个新的贷款(在我的控制器中使用下面的方法)时,对于唯一的卡片和人员都能正常工作,但一旦我尝试为某个特定的人员或卡片添加第二个贷款,就会出现“非唯一”的错误。显然,它不是唯一的,这正是问题所在,但我以为SQLAlchemy会自动处理这些后台的事情,把正确的现有人员或卡片ID作为外键添加到新的贷款中,而不是试图创建新的人员和卡片记录。难道我需要自己查询数据库来检查主键的唯一性并手动处理吗?我觉得这应该是SQLAlchemy能够自动处理的事情吧?

def addLoan(session, data):

    loan = Loan()
    loan.date_due = data["loan"]["date_due"]
    loan.date_issued = data["loan"]["date_issued"]
    loan.issue_reason = data["loan"]["issue_reason"]

    person = Person()
    person.id = data["person"]["id"]
    person.fname = data["person"]["fname"]
    person.sname = data["person"]["sname"]
    loan.person = person

    card = Card()
    card.id = data["card"]["id"]
    loan.card = card

    session.add(loan)
    session.commit()

在MediaLocker的例子中,即使是重复的记录也会创建一个自增的主键(这不符合规范化规则)。我想要一个规范化的数据库(即使在一个小项目中,这也是学习的最佳实践),但在网上找不到任何可以研究的例子。

我该如何实现以上目标?

1 个回答

10

在你尝试添加一个主键重复的新对象之前,你需要先获取并分配已经存在的 PersonCard 对象到这个关系中。你可以通过对你的代码做一些小改动来实现这一点。

def addLoan(session, data):

    loan = Loan()
    loan.date_due = data["loan"]["date_due"]
    loan.date_issued = data["loan"]["date_issued"]
    loan.issue_reason = data["loan"]["issue_reason"]

    person = session.query(Person).get(data["person"]["id"])
    if not person:
        person = Person()
        person.id = data["person"]["id"]
        person.fname = data["person"]["fname"]
        person.sname = data["person"]["sname"]
    loan.person = person

    card = session(Card).query.get(data["card"]["id"])
    if not card:
        card = Card()
        card.id = data["card"]["id"]
    loan.card = card

    session.add(loan)
    session.commit()

如果你想把这个过程简化成一步,还有一些关于 get_or_create 函数的解决方案。

如果你是从零开始往一个新数据库里加载大量记录,而你的查询比 get 更复杂(因为会话对象本身应该能缓存 get 的查找),你可以通过将每个新创建的 Person 和 Card 对象按 ID 添加到一个临时字典中,从而避免查询数据库,虽然这样会占用一些内存。然后你可以直接从这个字典中获取已经存在的对象,而不是去数据库里查。

撰写回答