【问题标题】:SQLAlchemy - eager load for many to many secondary relation not working as expectedSQLAlchemy - 多对多二级关系的急切负载未按预期工作
【发布时间】:2021-11-27 09:19:54
【问题描述】:

在我们的系统中,我们有实体 Item 和 Store,它们与 Stock 实体相关。 一件商品可以存放在多个商店,也可以存放多个商品在一个商店,所以是简单的多对多关系。

但是,当使用辅助参考描述这种关系时:

    stores = relationship(
        'Store',
        secondary='stock',
        backref='items'
    )

SQLAlchemy 会加载该相关 Store 的所有袜子,而不仅仅是那些与引用项目相关的袜子。

例如。当我们指定一个生成以下 sql 的关系时:

SELECT item.id AS item_id, store.id AS store_id, stock.id AS stock_id, stock.store_id AS stock_store_id, stock.item_id AS stock_item_id
FROM item 
LEFT OUTER JOIN (stock AS stock_1 JOIN store ON store.id = stock_1.store_id) ON item.id = stock_1.item_id 
LEFT OUTER JOIN stock ON store.id = stock.store_id AND stock.item_id = item.id 
WHERE stock.item_id = item.id

返回以下数据:

item_id, store_id, stock_id, stock_store_id, stock_item_id,
      1,        1,        1,              1,             1
      2,        1,        2,              1,             2
      1,        2,        3,              2,             1
      2,        2,        4,              2,             2

实际加载的数据如下:

items = [{
  id: 1,
  stores: [
    {
      id: 1,
      stocks: [
        { id: 1, item_id: 1 },
        { id: 2, item_id: 2 } <- should not be loaded items[0].id != 2
      ] 
    },
    {
      id: 2,
      stocks: [
        { id: 3, item_id: 1 },
        { id: 4, item_id: 2 } <- should not be loaded items[0].id != 2
      ] 
    }
  ]
},
{
  id: 2,
  stores: [
    {
      id: 1,
      stocks: [
        { id: 2, item_id: 2 },
        { id: 1, item_id: 1 } <- should not be loaded items[1].id != 1
      ] 
    },
    {
      id: 2,
      stocks: [
        { id: 4, item_id: 2 },
        { id: 3, item_id: 1 } <- should not be loaded items[1].id != 1
      ] 
    }
  ]
}]

作为参考,请查看实体及其关系的声明以及查询对象:

Base = declarative_base()

class Item(Base):
    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)

    stores = relationship(
        'Store',
        secondary='stock',
        backref='items'
    )

class Store(Base):
    __tablename__ = 'store'
    id = Column(Integer, primary_key=True)

class Stock(Base):
    __tablename__ = 'stock'
    id = Column(Integer, primary_key=True)
    store_id = Column(Integer, ForeignKey(Store.id), nullable=False)
    item_id = Column(Integer, ForeignKey(Item.id), nullable=False)

    item = relationship(Item, backref='stocks')
    store = relationship(Store, backref='stocks')

items = session.query(
    Item
).outerjoin(
    Item.stores,
    (Stock, and_(Store.id == Stock.store_id, Stock.item_id == Item.id))
).filter(
    Stock.item_id == Item.id,
).options(
    contains_eager(
        Item.stores
    ).contains_eager(
        Store.stocks
    )
).all()

【问题讨论】:

    标签: python postgresql sqlalchemy flask-sqlalchemy eager-loading


    【解决方案1】:

    这是因为具有相同id 的商店是相同的Store 实例。

    在序列化/显示结果时进行显式过滤可能会更好。

    也就是说,可以覆盖Item__getattribute__ 以拦截Item.stores 以返回_ItemStore 包装器,它只返回stocks 与父item_id 相同的item_id

    class Item(Base):
        # ...
    
        class _ItemStore:
            def __init__(self, store, item_id):
                self.id = store.id
                self._item_id = item_id
                self._store = store
    
            @property
            def stocks(self):
                return [stock for stock in self._store.stocks if stock.item_id == self._item_id]
    
        def __getattribute__(self, item):
            value = super().__getattribute__(item)
            if item == 'stores':
                value = [self._ItemStore(store, self.id) for store in value]
            return value
    

    添加一个简单的缓存以便item.stores == item.stores:

    def __getattribute__(self, item):
        value = super().__getattribute__(item)
        if item == 'stores':
            cache = getattr(self, '_stores', None)
            if cache is None:
                cache = self._stores = {}
            item_id = self.id
            item_store_cls = self._ItemStore
            value = [cache.setdefault(id(store), item_store_cls(store, item_id)) for store in value]
        return value
    

    【讨论】:

    • 非常感谢!这使它适用于我的基本示例,我们将尝试将其应用于我们的更复杂的模型集。这很有帮助!
    猜你喜欢
    • 2019-08-09
    • 2016-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-03
    • 2018-12-28
    • 2014-10-13
    • 1970-01-01
    相关资源
    最近更新 更多