SQLAlchemy的:二次关系更新(Sqlalchemy: secondary relations

2019-08-01 06:22发布

我有两个表,说A和B都具有一个主键ID。 他们有许多一对多的关系,美国证券交易委员会。

SEC = Table('sec', Base.metadata,
    Column('a_id', Integer, ForeignKey('A.id'), primary_key=True, nullable=False),
    Column('b_id', Integer, ForeignKey('B.id'), primary_key=True, nullable=False)
)

class A():
   ...
   id = Column(Integer, primary_key=True) 
   ...
   rels = relationship(B, secondary=SEC)

class B():
   ...
   id = Column(Integer, primary_key=True) 
   ...

让我们看看这段代码。

a = A()
b1 = B()
b2 = B()
a.rels = [b1, b2]
...
#some place later
b3 = B()
a.rels = [b1, b3]  # errors sometimes

有时候,我会在最后一行的错误说

duplicate key value violates unique constraint a_b_pkey

在我的理解,我认为它会尝试添加(a.id,b.id)到“秒”表又造成了独特的约束错误。 是,它是什么呢? 如果是这样,我怎么能避免这种情况? 如果不是,为什么我有这样的错误?

Answer 1:

问题是你要确保你创建实例是唯一的。 我们可以创建一个可选的构造,检查现有的未提交实例的高速缓存或查询返回一个新实例之前,现有COMMITED实例的数据库。

这是这种方法的演示:

from sqlalchemy import Column, Integer, String, ForeignKey, Table
from sqlalchemy.engine import create_engine
from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.orm import sessionmaker, relationship

engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(engine)
Base = declarative_base(engine)

session = Session()


class Role(Base):
    __tablename__ = 'role'

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False, unique=True)

    @classmethod
    def get_unique(cls, name):
        # get the session cache, creating it if necessary
        cache = session._unique_cache = getattr(session, '_unique_cache', {})
        # create a key for memoizing
        key = (cls, name)
        # check the cache first
        o = cache.get(key)
        if o is None:
            # check the database if it's not in the cache
            o = session.query(cls).filter_by(name=name).first()
            if o is None:
                # create a new one if it's not in the database
                o = cls(name=name)
                session.add(o)
            # update the cache
            cache[key] = o
        return o


Base.metadata.create_all()

# demonstrate cache check
r1 = Role.get_unique('admin')  # this is new
r2 = Role.get_unique('admin')  # from cache
session.commit()  # doesn't fail

# demonstrate database check
r1 = Role.get_unique('mod')  # this is new
session.commit()
session._unique_cache.clear()  # empty cache
r2 = Role.get_unique('mod')  # from database
session.commit()  # nop

# show final state
print session.query(Role).all()  # two unique instances from four create calls

create_unique方法是由激发从SQLAlchemy的维基例子 。 这个版本是令人费解的要少得多,这有利于在灵活简便。 我已经没有任何问题的生产系统中使用它。

明明有可添加的改进; 这只是一个简单的例子。 该get_unique方法可以从继承UniqueMixin ,用于任意数量的模型。 论据更灵活memoizing无法实施。 这也搁置多个线程插入由蚂蚁Aasma提到冲突的数据的问题; 处理是比较复杂的,但应该是一个明显的扩展。 我留给你。



Answer 2:

你提到的错误确实是从插入冲突的价值的秒表。 可以肯定,这是从你觉得是不是以前的一些变化,打开SQL日志记录,并检查什么样的价值观是它试图示数之前插入操作。

当重写许多一对多的收藏价值,SQLAlchemy的集合的新内容与国家的数据库进行比较,并相应问题删除和插入语句。 除非你是在SQLAlchemy的内部闲逛,应该有遇到这个问题的两种方式。

第一是并发的修改:方法1取得的值a.rels并注意到它是空的,同时进程2也取a.rels,它设置为[B1,B2]和提交冲洗(A,B1),(一, b2)的元组,方法1套a.rels至[B1,B3]注意到,以前的内容是空的,当它试图冲洗秒元组(A,B1)它获取重复的键错误。 在这种情况下,正确的操作通常是从顶部重试事务。 您可以使用序列化的事务隔离在这种情况下是从业务逻辑错误造成明显重复键错误,而不是得到一个序列化错误。

当你成功地说服SQLAlchemy的,你不需要通过设置RELS的加载策略属性知道数据库状态第二种情况发生的noload 。 这可以通过定义加入的关系时,可以完成lazy='noload'参数,或查询时,调用.options(noload(A.rels))上的查询。 SQLAlchemy的假定秒表具有装入该策略生效的对象没有匹配行。



文章来源: Sqlalchemy: secondary relationship update