如何仅加载指定列并获取模型对象?(不使用延迟)

1 投票
1 回答
1097 浏览
提问于 2025-04-18 18:52

我想要:

  1. 只加载我需要的字段,从多个表或模型中。
  2. 得到的是“模型对象”,而不是通过session.query传递列名后得到的类似命名元组的对象。这个过程应该能够跨越关系,比如说employee.company.name,这里的company是员工模型中的一个关系字段。
  3. 防止在初始选择查询后,意外加载其他字段。我可以使用defer或者.load_only('field_name'),但这样的话,其他人可能会访问我没有指定的模型属性,这样就会导致另一个查询被执行。理想情况下,访问一个在查询中没有指定的字段时,应该抛出一个AttributeError,即使这个字段在模型中是定义过的。

使用SqlAlchemy提供的机制,这样做可能吗?这样做是否是个好主意?

我写了一个函数来实现我想要的功能,但看起来其他人可能有更好、更标准的解决方案。

class Attributable(object):
    pass


def spread_result(row, columns):
    """
        :type row: sqlalchemy.util._collections.KeyedTuple
        :type columns: tuple
        Usage:
        >>> result = session.query(Model.field, AnotherModel.other_field).first()
        >>> obj = spread_result(result, ('field', 'another_model.other_field'))
        >>> obj.field
        'field_value'
        >>> obj.another_model.other_field
        'other_field_value'
        >>> obj.another_mapped_field
        AttributeError: 'Attributable' object has no attribute 'another_mapped_field'
    """
    root = Attributable()
    for column, value in zip(columns, row):
        obj = root
        parts = column.split('.')
        for i, attr in enumerate(parts):
            if i == len(parts) - 1:
                setattr(obj, attr, value)
            else:
                setattr(obj, attr, Attributable())
                obj = getattr(obj, attr)

    return root

1 个回答

1

最简单的方法就是创建一个“公共”模型,这个模型映射到同一张表,但只包含你想要加载或访问的列和其他属性。

相关文档提到:

include_properties 或 exclude_properties 参数可以指定只映射部分列。

如果你有一个 Person 模型,用户只需要看到 id 和 name,那么这个“公共”类看起来会是这样的:

class PublicPerson(Base):
    __table__ = Person.__table__

    __mapper_args__ = {
        'include_properties': ['id', 'name']
    }

这里有一个简单的可运行示例:

from datetime import datetime
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session

engine = create_engine('sqlite://', echo=True)
session = Session(bind=engine)
Base = declarative_base(bind=engine)


class Person(Base):
    __tablename__ = 'person'

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


class PublicPerson(Base):
    __table__ = Person.__table__

    __mapper_args__ = {
        'include_properties': ['id', 'name']
    }


Base.metadata.create_all()

session.add(Person(name='Jan', birthday=datetime(2001, 1, 1)))

# query the full person, prints birthday
print(session.query(Person.birthday).scalar())

# query the "public" person, raises exception on birthday
print(session.query(PublicPerson.birthday).scalar())

撰写回答