使用 SQLAlchemy 关联代理强制唯一性
Posted
技术标签:
【中文标题】使用 SQLAlchemy 关联代理强制唯一性【英文标题】:Enforcing uniqueness using SQLAlchemy association proxies 【发布时间】:2015-06-10 00:21:07 【问题描述】:我正在尝试使用关联代理来更简单地处理标签式记录,但我遇到了一个问题,即强制唯一性和让对象重用现有标签而不是总是创建新标签。
这是一个类似于我的设置。文档中的示例有一些 enforcing uniqueness 的配方,但它们都依赖于对会话的访问,并且通常需要一个全局会话,而我无法做到这一点。
from sqlalchemy import Column, Integer, String, create_engine, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
Base = declarative_base()
engine = create_engine('sqlite://', echo=True)
Session = sessionmaker(bind=engine)
def _tag_find_or_create(name):
# can't use global objects here, may be multiple sessions and engines
# ?? No access to session here, how to do a query
tag = session.query(Tag).filter_by(name=name).first()
tag = Tag.query.filter_by(name=name).first()
if not tag:
tag = Tag(name=name)
return tag
class Item(Base)
__tablename__ = 'item'
id = Column(Integer, primary_key=True)
tags = relationship('Tag', secondary='itemtag')
tagnames = association_proxy('tags', 'name', creator=_tag_find_or_create)
class ItemTag(Base)
__tablename__ = 'itemtag'
id = Column(Integer, primary_key=True)
item_id = Column(Integer, ForeignKey('item.id'))
tag_id = Column(Integer, ForeignKey('tag.id'))
class Tag(Base)
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
# Scenario 1
session = Session()
item = Item()
session.add(item)
item.tagnames.append('red')
# Scenario 2
item2 = Item()
item2.tagnames.append('blue')
item2.tagnames.append('red')
session.add(item2)
如果没有 creator 功能,我只会得到大量重复的 Tag 项。 creator 函数似乎是进行此类检查的最明显位置,但我不确定如何从 creator 函数内部进行查询。
考虑示例底部提供的两种方案。在第一个示例中,似乎应该有一种方法可以在 creator 函数中访问会话,因为要添加标签的对象已经与会话相关联。
在第二个示例中,Item 对象尚未与会话关联,因此验证检查无法在 creator 函数中进行。当对象实际添加到会话中时,它必须稍后发生。
对于第一种情况,我将如何访问 creator 函数中的会话对象?
对于第二种情况,有没有办法“监听” 何时将父对象添加到会话并在那时验证关联代理?
【问题讨论】:
【参考方案1】:对于第一种情况,您可以使用object_session
。
至于整体问题:是的,您需要访问当前会话;如果在您的应用程序中使用 scoped_session
是合适的,那么您链接到的食谱的第二部分应该可以正常使用。请参阅Contextual/Thread-local Sessions 了解更多信息。
在事件从 transient 变为 persistent 状态时处理事件和更改对象不会使您的代码漂亮或非常健壮。所以我会立即将新的Tag
对象添加到会话中,如果事务回滚,它们就不会在数据库中。
请注意,在多用户环境中,您可能会遇到竞争条件:同一个标签是新标签,并且由两个用户同时创建。最后提交的用户将失败(如果您对数据库有唯一约束)。 在这种情况下,您可能会考虑没有唯一约束,并有一个(每日)程序来清理这些重复项(并重新分配关系)。随着时间的推移,新物品会越来越少,发生这种冲突的可能性也会越来越小。
【讨论】:
如何访问 creator 函数中的 Item 对象以使用object_session
?有没有办法告诉 Association_proxy 将父对象传递给创建者函数?
例如,您可以在Item
: def add_tag(self, tag_name): object_session(self).query(...) ...
上创建一个辅助方法,在这种情况下您可以跳过关联代理。使用关联代理,您可以创建自己的子类和 intercept 修改操作。但实际上,我认为UniqueObject
recipe 应该适合上下文绑定会话。以上是关于使用 SQLAlchemy 关联代理强制唯一性的主要内容,如果未能解决你的问题,请参考以下文章