【问题标题】:Association Proxy SQLAlchemy关联代理 SQLAlchemy
【发布时间】:2015-09-13 23:22:30
【问题描述】:

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

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

所以在我的情况下,这仅作为视图有用,我需要使用 ORM 查询来检索要附加的对象。

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

例如来自文档:

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!

我想这甚至可能不是一个好主意,但在某些用例中可能是这样的:)

【问题讨论】:

    标签: sqlalchemy


    【解决方案1】:

    您链接到的文档页面上显示的示例是 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 
                )
    

    【讨论】:

    • 我相信答案显示的内容与文档完全相同,但文档向您展示了如何在不覆盖类上的多个方法的情况下做到这一点(我假设这就是类中的方法正在做的事情,我没有检查)。我的问题更多是关于更改该示例的行为,使其更符合我对特定用例的要求(不是每次添加时都创建一个新对象,而是使用数据库中的现有对象(如果可以))。
    • @DerekLitz:很公平...将根据您问题中链接到的文档更改答案...
    • 是的,该方法看起来会起作用,并且优于仅定义一个 __init__ 会做同样的事情,因为这会使所有对象都必须以这种方式实例化,或者给方法增加一些复杂性。不过,我注意到了一些错误,这是我不太确定的问题之一。获取会话以便在插入之前进行查询的最佳方法是什么?您的代码在查询中有一个错误,因为它需要通过对象上的会话来完成,例如session.query(Keyword).filter(Keyword.keyword=kw)
    • 错误?真的吗?首先,您可以使用object_session (docs.sqlalchemy.org/en/latest/orm/…) 从对象中获取会话并执行查询。但是,如果您使用 declarative 扩展名(在您引用的示例中使用),在简单场景(只有一个会话)中很常见,只需执行 Base.query = session.query_property(),这就是我在示例代码中使用的回答..
    • 非常好,我不知道 ORM 对象包含作为其状态的一部分附加到的会话。因为,将关键字附加到附加到会话的对象上才有意义(想不出它不会出现的情况),这将很方便。我更喜欢使用它而不是做一些猴子补丁的想法:)
    【解决方案2】:

    我最近遇到了同样的问题。 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
    

    【讨论】:

      猜你喜欢
      • 2013-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多