为什么 SQLAlchemy InstrumentedList 的顺序不持久?

Mar*_* B. 5 python database sqlalchemy list

快速摘要:我想要在 SQLAlchemy 中拥有一个有序的地址列表。但当我提交时,我的列表顺序会发生变化。为什么会发生这种情况以及如何改变它?

长解释:

  1. 我从附加到用户对象的地址列表开始。
  2. 然后我用新地址替换“地址”列表的第一个元素。
  3. 然后我打印地址列表......到目前为止,顺序是我所期望的。
  4. 最后我承诺了。提交后,我进行了查询,但地址列表的顺序已更改。

那么这是否只是我不理解的关于数据库的一般问题?或者 SQLAlchemy InstrumentedList 的行为不像实际列表?我以为我可以改变一段关系中元素的顺序,但我不知道如何改变。


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

Base = declarative_base()
Session = sessionmaker()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    fullname = Column(String(50))
    password = Column(String(12))

    addresses = relationship("Address", back_populates="user")

    def __repr__(self):
        return "<User(name='%s', fullname='%s', password='%s')>" % (
                                self.name, self.fullname, self.password)

class Address(Base):
    __tablename__ = 'addresses'
    id = Column(Integer, primary_key=True)
    email_address = Column(String, nullable=False)
    user_id = Column(Integer, ForeignKey('users.id'))

    user = relationship("User", back_populates="addresses")

    def __repr__(self):
        return "<Address(email_address='%s')>" % self.email_address


if __name__ == "__main__":
    engine = create_engine('sqlite:///:memory:', echo=False)
    Session.configure(bind=engine)
    Base.metadata.create_all(engine)
    session = Session()

    user = User(name='ed', fullname='Ed Jones', password='edspassword')
    user.addresses = [Address(email_address='jack@google.com'), Address(email_address='j25@yahoo.com')]
    session.add(user)
    session.commit()

    user = session.query(User).filter_by(name='ed').first()

    print("Current order of addresses list at start.")
    print(user.addresses)
    print()

    new_primary_address = Address(email_address='primary@google.com')
    user.addresses[0] = new_primary_address

    print("Current order of addresses list before commit.")
    print("But after chaning addresses[0].")
    print(user.addresses)
    print()

    session.commit()

    user = session.query(User).filter_by(name='ed').first()

    print("Current order of addresses list after commit.")
    print(user.addresses)
    print()

    print("Why is the order of the InstrumentedList not persistent?")
    print("Isn't persistent order what makes a list a list?")
Run Code Online (Sandbox Code Playgroud)

Ilj*_*ilä 5

一般而言,它是“数据库”。An 的InstrumentedList行为确实类似于list添加了 ORM 工具的 Python 端,但是当您提交时,的Session默认行为是使 ORM 管理的属性的所有数据库加载状态过期,因此必须在下次访问时刷新列表。这意味着 SELECT 例如

2017-05-21 13:32:31,124 INFO sqlalchemy.engine.base.Engine SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id 
FROM addresses 
WHERE ? = addresses.user_id
Run Code Online (Sandbox Code Playgroud)

发出以获取列表内容。在 SQL 中,如果没有显式选择,则SELECT 的顺序是未指定的,因此您可能会或可能不会按照与以前相同的顺序获取项目。另请注意,ORM 操作

user.addresses[0] = new_primary_address
Run Code Online (Sandbox Code Playgroud)

转换为一个 UPDATE,将user_id旧地址元组设置为 NULL 并在表中插入一个新地址元组,因此即使按插入顺序返回行,您也不会得到您想要的顺序。

如果地址顺序对您很重要,则必须选择排序。使用order_by参数relationship

class User(Base):
    ...
    addresses = relationship("Address", back_populates="user",
                             order_by="Address.email_address")
Run Code Online (Sandbox Code Playgroud)

获取时将按电子邮件地址对地址进行排序。SQLAlchemy 还提供(感谢您的挖掘)用于可变有序关系的辅助集合类:orderinglist,如果用作排序,它有助于管理更改的索引/位置。

您似乎希望地址顺序来表示哪个是用户的主要地址。单独的标志列可以更好地实现这一点。

  • 在“订购列表”上找到的很好,以前不知道。如果您不介意,我想将其包含在答案中。 (2认同)