关联代理SQLAlchemy

时间:2012-03-12 20:17:49

标签: sqlalchemy

This source详细说明了如何使用关联代理来创建具有ORM对象值的视图和对象。

但是,当我追加一个与数据库中现有对象匹配的值(并且所述值是唯一的或主键)时,它会创建一个冲突的对象,因此我无法提交。

所以在我看来这只是一个有用的视图,我需要使用ORM查询来检索要追加的对象。

这是我唯一的选择还是我可以使用merge(如果它是主键而不是唯一约束,我可能只能这样做),或者设置构造函数以便它将使用数据库中的现有对象如果它存在而不是创建一个新对象?

例如来自文档:

user.keywords.append('cheese inspector')

# Is translated by the association proxy into the operation:

user.kw.append(Keyword('cheese inspector'))

但是我想要翻译成更像的东西:(当然查询可能会失败)。

keyword = session.query(Keyword).filter(Keyword.keyword == 'cheese inspector').one()
user.kw.append(keyword)

或理想情况

user.kw.append(Keyword('cheese inspector'))
session.merge() # retrieves identical object from the database, or keeps new one
session.commit() # success!

我认为这可能不是一个好主意,但它可能在某些用例中:)

2 个答案:

答案 0 :(得分:9)

您链接到的文档页面上显示的示例是composition类型的关系(以OOP术语表示),因此代表owns类型的关系,而不是uses的关系动词因此,每个owner都有自己的相同(就值而言)关键字的副本。

实际上,您可以完全使用您在问题中链接到的文档中的建议来创建自定义creator方法,并将其破解为重用给定键的现有对象,而不是仅创建新对象。在这种情况下,User类和creator函数的示例代码如下所示:

def _keyword_find_or_create(kw):
    keyword = Keyword.query.filter_by(keyword=kw).first()
    if not(keyword):
        keyword = Keyword(keyword=kw)
        # if aufoflush=False used in the session, then uncomment below
        #session.add(keyword)
        #session.flush()
    return keyword

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String(64))
    kw = relationship("Keyword", secondary=lambda: userkeywords_table)
    keywords = association_proxy('kw', 'keyword', 
            creator=_keyword_find_or_create, # @note: this is the 
            )

答案 1 :(得分:3)

我最近遇到了同样的问题。 SQLAlchemy的创建者Mike Bayer向我推荐“unique object” recipe,但也向我展示了一个使用事件监听器的变体。后一种方法修改关联代理,以便UserKeyword.keyword临时指向普通字符串,并且只有在关键字尚不存在时才创建新的关键字对象。

from sqlalchemy import event

# Same User and Keyword classes from documentation

class UserKeyword(Base):
    __tablename__ = 'user_keywords'

    # Columns
    user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
    keyword_id = Column(Integer, ForeignKey(Keyword.id), primary_key=True)
    special_key = Column(String(50))

    # Bidirectional attribute/collection of 'user'/'user_keywords'
    user = relationship(
        User,
        backref=backref(
            'user_keywords',
            cascade='all, delete-orphan'
            )
        )

    # Reference to the 'Keyword' object
    keyword = relationship(Keyword)

    def __init__(self, keyword=None, user=None, special_key=None):
        self._keyword_keyword = keyword_keyword  # temporary, will turn into a
                                                 # Keyword when we attach to a 
                                                 # Session
        self.special_key = special_key

    @property
    def keyword_keyword(self):
        if self.keyword is not None:
            return self.keyword.keyword
        else:
            return self._keyword_keyword

    @event.listens_for(Session, "after_attach")
    def after_attach(session, instance):
        # when UserKeyword objects are attached to a Session, figure out what 
        # Keyword in the database it should point to, or create a new one
        if isinstance(instance, UserKeyword):
            with session.no_autoflush:
                keyword = session.query(Keyword).\
                    filter_by(keyword=instance._keyword_keyword).\
                    first()
                if keyword is None:
                    keyword = Keyword(keyword=instance._keyword_keyword)
                instance.keyword = keyword