sqlalchemy 创建关系时出现错误:映射器上已存在该名称的属性

29 投票
3 回答
32461 浏览
提问于 2025-05-01 01:41

我有两个非常简单的模型。在我的 Post 模型中,应该有两个与 User 表的关系。一个是帖子拥有者,另一个是最后编辑这个帖子的用户。它们的值可以不同,但都是指向同一个 User 表。

我的模型设置如下:

class Post(Base):
    last_editor_id = Column(BigInteger, ForeignKey('users.id'), nullable=True)
    last_editor = relationship('User', backref='posts', foreign_keys=[last_editor_id])
    owner_id = Column(BigInteger, ForeignKey('users.id'), nullable=False, index=True)
    owner = relationship('User', backref='posts', foreign_keys=[owner_id])
    
class User(Base):
    '''This represents a user on the site'''
    __tablename__ = 'users'
    id = Column(BigInteger, primary_key=True, unique=True)
    name = Column(BigInteger, nullable=False)

但是,当我尝试创建这些模型时,我遇到了以下错误:

sqlalchemy.exc.ArgumentError: 创建关系 'Post.owner' 的反向引用 'posts' 时出错:该名称的属性在映射器 'Mapper|User|users' 上已存在。

我该如何修正这个问题,以便在 Post 模型中保留两个外键呢?

暂无标签

3 个回答

0

你可以修正这个错误,并在Post模型中同时保持两个外键,方法是使用独特的backref名称

正确的代码应该是这样的
class Post(Base):
    last_editor_id = Column(BigInteger, ForeignKey('users.id'), nullable=True)
    last_editor = relationship('User', backref='edited_posts', foreign_keys=[last_editor_id])
    owner_id = Column(BigInteger, ForeignKey('users.id'), nullable=False, index=True)
    owner = relationship('User', backref='owned_posts', foreign_keys=[owner_id])

class User(Base):
    '''This represents a user on the site'''
    __tablename__ = 'users'
    id = Column(BigInteger, primary_key=True, unique=True)
    name = Column(BigInteger, nullable=False)

为什么

  1. backref会自动在用户那边创建一个关系,但这种方式不够明确,可能在复杂的情况下让人感到困惑。

我把backref的名称改成了'edited_posts',用于last_editor的关系;把'owned_posts',用于拥有者的关系。

现在,当你访问某个用户的帖子时,可以用user.edited_posts来获取这个用户编辑过的帖子,用user.owned_posts来获取这个用户拥有的帖子。

6

总的来说,这是一个关于反向引用命名的问题。

因为1对多的关系有时候会让人感到困惑,所以我总是把关系属性放在单数那一边,以避免混淆。

这样,反向引用的名称总是单数的,而关系属性总是在外键所指向的类中。

接下来是我对修复代码的建议:

class Post(Base):
    last_editor_id = Column(BigInteger, ForeignKey('users.id'), nullable=True)

    owner_id = Column(BigInteger, ForeignKey('users.id'), nullable=False, index=True)


class User(Base):
    '''This represents a user on the site'''
    __tablename__ = 'users'
    id = Column(BigInteger, primary_key=True, unique=True)
    name = Column(BigInteger, nullable=False)
    owned_posts = relationship('Post', backref='owner')
    edited_posts = relationship('Post', backref='last_editor')

现在你可以通过 User.owned_posts 获取一个 User 拥有的所有帖子,也可以通过 Post.owner 获取一个帖子的所有者。最后编辑的属性也是一样的。

如果想了解更多信息,可以查看 文档,了解如何设置关系。

43

这个错误告诉你,你在反向引用中多次使用了post这个名字。你只需要给反向引用起个独特的名字就可以了。下面是一个完整的例子——我在Post类中添加了一个主键id,并且加了一些__repr__方法,这样我们就能看到更易读的输出。

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, BigInteger, ForeignKey, Integer
from sqlalchemy.orm import relationship, sessionmaker

Base = declarative_base()
engine = create_engine('sqlite://') ## In Memory.
Session = sessionmaker()
Session.configure(bind=engine)
session = Session()

class Post(Base):
    __tablename__ = 'post'
    id = Column(Integer, primary_key=True)
    last_editor_id = Column(BigInteger, ForeignKey('users.id'), nullable=True)
    last_editor = relationship('User', backref='editor_posts', foreign_keys=[last_editor_id])
    owner_id = Column(BigInteger, ForeignKey('users.id'), nullable=False, index=True)
    owner = relationship('User', backref='owner_posts', foreign_keys=[owner_id])

    def __repr__(self):
        return '<Post: {}>'.format(self.id)


class User(Base):
    '''This represents a user on the site'''
    __tablename__ = 'users'
    id = Column(BigInteger, primary_key=True, unique=True)
    name = Column(BigInteger, nullable=False)

    def __repr__(self):
        return '<User: {}>'.format(self.name)



Base.metadata.create_all(engine)

bob = User(name='Bob', id=1)
alice = User(name='Alice', id=2)
post = Post(owner=alice, last_editor=bob, id=1)

session.add(post)
session.commit()

bob = session.query(User).get(1)
print(bob)
# <User: Bob>
print(bob.editor_posts)
# [<Post: 1>]
print(bob.owner_posts)
# []

post = session.query(Post).get(1)
print(post.owner)
# <User: Alice>
print(post.last_editor)
# <User: Bob>

现在,当你查询一个用户时,可以通过这个对象来访问user.owner_posts或者user.editor_posts

撰写回答