为什么SQLAlchemy/associationproxy会重复我的标签?

5 投票
2 回答
1172 浏览
提问于 2025-04-15 16:59

我正在尝试使用关联代理来处理标签,这个场景和文档中的例子非常相似。以下是我博客的部分数据库结构,使用的是声明式方式:

class Tag(Base):
    __tablename__ = 'tags'
    id            = Column(Integer, primary_key=True)
    tag           = Column(Unicode(255), unique=True, nullable=False)

class EntryTag(Base):
    __tablename__ = 'entrytags'
    entry_id      = Column(Integer, ForeignKey('entries.id'), key='entry', primary_key=True)
    tag_id        = Column(Integer, ForeignKey('tags.id'), key='tag', primary_key=True)

class Entry(Base):
    __tablename__ = 'entries'
    id            = Column(Integer, primary_key=True)
    subject       = Column(Unicode(255), nullable=False)
    # some other fields here
    _tags         = relation('Tag', backref='entries', secondary=EntryTag.__table__)
    tags          = association_proxy('_tags','tag')

这是我尝试使用它的方式:

>>> e = db.query(Entry).first()
>>> e.tags
[u'foo']
>>> e.tags = [u'foo', u'bar']  # really this is from a comma-separated input
db.commit()
Traceback (most recent call last):
[...]
sqlalchemy.exc.IntegrityError: (IntegrityError) duplicate key value violates unique constraint "tags_tag_key"
 'INSERT INTO tags (id, tag) VALUES (%(id)s, %(tag)s)' {'tag': 'bar', 'id': 11L}
>>> map(lambda t:(t.id,t.tag), db.query(Tag).all())
[(1, u'foo'), (2, u'bar'), (3, u'baz')]

标签 u'bar' 已经存在,ID 是 2;为什么 SQLAlchemy 不直接使用这个,而是试图创建一个新的呢?我的数据库结构是不是有什么问题?

2 个回答

0

我之前从来没有用过SQLAlchemy 0.5(我最后一个用它的应用是基于0.4的),但我能看到你代码中的一个小问题:你应该修改这个association_proxy对象,而不是重新给它赋值。

你可以试试这样做:

e.tags.append(u"bar")

而不是这样:

e.tags = ...

如果这样还不行,试着贴出一个完整的可运行示例,包括那些表的代码(记得把导入的部分也加上!),我会给你更多建议。

3

免责声明:我已经很久没用过SQLAlchemy了,所以这只是我的猜测。

看起来你希望SQLAlchemy能神奇地把字符串'bar'查找成相关的Tag,然后在多对多的表里插入数据。但我觉得这样做是不对的,因为你提到的字段('tag')并不是主键。

想象一下,如果你的Tag表其实是Comment表,里面也有id和text字段。你可能希望用和上面一样的方式e.comments = ['u'Foo', 'u'Bar']来添加评论到一个条目,但你希望它只是执行插入,而不是去检查是否已经有相同内容的评论。

所以这里可能也是这样,但它遇到了你标签名称的唯一性限制,导致操作失败,因为它认为你在做错事。

那怎么解决呢?把tags.tag设为主键可能是正确的做法,虽然我不知道这样做效率如何,SQLAlchemy处理得怎么样。如果不这样做,可以先通过名称查询Tag对象,然后再把它们分配给条目。你可能需要写一个小工具函数,接收一个unicode字符串,要么返回一个已有的Tag,要么为你创建一个新的。

撰写回答