Sqlalchemy 联想炼金术

Sqlalchemy 联想炼金术,sqlalchemy,Sqlalchemy,详细说明如何使用关联代理创建具有ORM对象值的视图和对象 但是,当我附加一个与数据库中现有对象匹配的值(并且该值是唯一的或主键)时,它会创建一个冲突的对象,因此我无法提交 因此,在我的例子中,这只是一个有用的视图,我需要使用ORM查询来检索要追加的对象 这是我唯一的选择,还是我可以使用merge(只有当它是主键而不是唯一约束时,我才能这样做),或者设置构造函数,使其在数据库中使用现有对象(如果存在),而不是创建新对象 例如,从文档中: user.keywords.append('cheese i

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

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

因此,在我的例子中,这只是一个有用的视图,我需要使用ORM查询来检索要追加的对象

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

例如,从文档中:

user.keywords.append('cheese inspector')

# Is translated by the association proxy into the operation:

user.kw.append(Keyword('cheese inspector'))
但我想被翻译成更像:(当然,查询可能会失败)

或者理想情况下

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

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

您链接到的文档页面上显示的示例是一种
组合
类型的关系(在OOP术语中),因此表示
拥有
类型的关系,而不是
使用
动词。因此,每个
所有者
都有自己的相同(价值)关键字副本

事实上,您可以使用您在问题中链接到的文档中的建议来创建一个自定义
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 
            )

我最近遇到了同样的问题。SQLAlchemy的创建者迈克·拜尔(Mike Bayer)向我介绍了事件监听器,但也向我展示了一个使用事件监听器的变体。后一种方法修改关联代理,以便
UserKeyword.keyword
临时指向一个普通字符串,并且仅在关键字不存在时创建一个新的关键字对象

来自sqlalchemy导入事件的

#文档中的相同用户和关键字类
类UserKeyword(基本):
__tablename\uuuu='user\u关键字'
#纵队
user\u id=Column(整数,ForeignKey(user.id),primary\u key=True)
关键字id=列(整数,外键(关键字.id),主键=真)
特殊_键=列(字符串(50))
#“用户”/“用户关键字”的双向属性/集合
用户=关系(
用户,
backref=backref(
“用户关键字”,
cascade='all,删除孤立'
)
)
#对“关键字”对象的引用
关键字=关系(关键字)
定义初始化(self,关键字=None,用户=None,特殊密钥=None):
self._keyword_keyword=关键字#临时,将变为
#当我们附加到
#会议
self.special_key=特殊_key
@财产
def关键字_关键字(self):
如果self.keyword不是None:
返回self.keyword.keyword
其他:
返回self.\u关键字\u关键字
@事件。侦听(会话“附加后”)
附加后的def(会话、实例):
#将UserKeyword对象附加到会话时,请确定
#关键字,或创建一个新的关键字
如果isinstance(实例,UserKeyword):
使用session.no_自动刷新:
关键字=session.query(关键字)\
筛选依据(关键字=实例.\u关键字\u关键字)\
第一()
如果关键字为“无”:
关键字=关键字(关键字=实例.\u关键字\u关键字)
instance.keyword=关键字

我相信答案与文档显示的内容完全相同,但文档向您展示了如何在不重写类上的几个方法的情况下执行此操作(我假设这些方法在类中就是这么做的,我没有检查)。我的问题更多的是改变那个例子的行为,使其更符合我的特定用例的要求(不是每次追加时都创建一个新的对象,而是如果可以的话使用db中的一个现有对象)。@DerekLitz:很公平。。。将更改答案,使其基于您问题中链接到的文档…是的,该方法看起来会起作用,并且优于仅定义一个
\uuuuu init\uuuuu
来做同样的事情,因为这将使所有对象都必须以这种方式实例化,或者为该方法增加一些复杂性。不过我注意到了一些错误,这是我不太确定的问题之一。为了在插入之前进行查询,获取会话的最佳方式是什么?您的代码在查询上有一个bug,因为它需要在对象上使用会话来完成,比如
session.query(关键字).filter(关键字.Keyword=kw)
。bug?真正地首先,您可以使用
object\u session
()从对象获取会话并执行查询。但是如果您使用
声明性
扩展(在您参考的示例中使用),则在简单场景中(仅一个会话)只需执行
Base.query=session.query\u property()
,这就是我在回答中的示例代码中使用的。非常好,我不知道ORM对象包含作为其状态的一部分附加到的会话。因为,只有将关键字附加到附加到会话的对象上才有意义(想不到在什么情况下不会这样),这将非常方便。我喜欢这样的想法,即使用它多一点,然后再做一些猴子修补:)
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 
            )