【问题标题】:SQLAlchemy loads unrelated cached objects when using contains_eagerSQLAlchemy 在使用 contains_eager 时加载不相关的缓存对象
【发布时间】:2021-03-13 22:32:24
【问题描述】:

我正在使用 SQLAlchemy 的 contains_eager 功能,当对象已加载到现有会话中时,我看到了奇怪的行为。具体来说,这些对象似乎没有像加载新数据时那样从关系集合中过滤出来。

这是一个最小的例子。步骤是

  1. 创建父子关系。
  2. 添加一个父级和两个具有不同values 的子级。
  3. 执行联合过滤查询,使用contains_eager 为父项加载匹配的子项。请注意,filter 应排除两个孩子之一。
  4. 观察到两个孩子都已填充到结果对象的children 属性中。
  5. 使用新的session,甚至调用session.expire_all()都可以得到正确的结果,说明问题是当前session中已经存在children。

这是预期的行为吗?如果是这样,打电话给expire_all 是避免这种情况的正确做法吗?

更一般地说,是否应该因此而避免使用contains_eager?如果必须在发出查询之前跟踪子对象是否已经存在,这似乎是抽象的中断。但也许我错过了什么。

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


create_statements = ["""
    DROP TABLE IF EXISTS child;
    """, """
    DROP TABLE IF EXISTS parent;
    """, """
    CREATE TABLE parent
    (
        id INTEGER NOT NULL PRIMARY KEY,
        name VARCHAR
    );
    """, """
    CREATE TABLE child
    (
        id INTEGER NOT NULL PRIMARY KEY,
        parent_id INTEGER REFERENCES parent(id),
        value INTEGER
    );
    """
]

Base = declarative_base()


class Parent(Base):
    __tablename__ = "parent"
    __table_args__ = {'implicit_returning': False}
    id = Column(Integer, primary_key=True)
    name = Column(String)

    children = relationship("Child", back_populates="parent")


class Child(Base):
    __tablename__ = "child"
    __table_args__ = {'implicit_returning': False}
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey(Parent.id))
    value = Column(Integer)

    parent = relationship(Parent, back_populates="children")


if __name__ == "__main__":
    engine = create_engine(f"sqlite:///")
    session = sessionmaker(bind=engine)()

    for statement in create_statements:
        session.execute(statement)

    p1 = Parent(id=1, name="A")
    c1 = Child(id=1, parent=p1, value=10)
    c2 = Child(id=2, parent=p1, value=20)
    session.add_all([p1, c1, c2])
    session.flush()
    # session.expire_all()  # Uncommenting this makes the below work as expected.

    results = session \
        .query(Parent) \
        .join(Child, Parent.id == Child.parent_id) \
        .options(
            contains_eager(Parent.children)
        ).filter(Child.value < 15) \
        .order_by(Parent.id) \
        .all()

    print(len(results[0].children))  # We should only have 1 child.
    print(all(c.value < 15 for c in results[0].children))  # All children should match the above filter condition.

【问题讨论】:

    标签: python orm sqlalchemy


    【解决方案1】:

    我在 SQLAlchemy GitHub 页面上asked this question。解决方案是在使用contains_eagerfilter 的任何查询上使用populate_existing。在我的具体示例中,此查询是正确的

    session \
            .query(Parent) \
            .join(Child, Parent.id == Child.parent_id) \
            .options(
                contains_eager(Parent.children)
            ).filter(Child.value < 15) \
            .order_by(Parent.id) \
            .populate_existing() \
            .all()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-05
      • 2020-12-26
      • 2023-02-02
      • 2016-03-25
      • 1970-01-01
      • 2019-11-12
      • 1970-01-01
      • 2020-06-09
      相关资源
      最近更新 更多