es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

¿Cómo deshabilitar la carga perezosa de SQLAlchemy?

Estoy desarrollando una aplicación en Python que usa Flask-SQLAlchemy y su mapeador ORM.
Tengo dos tablas con una relación de varios a uno. El problema principal es que quiero cargar el contenido de ambos objetos con una consulta JOIN y no más tarde, cuando accedo al objeto de la tabla secundaria.

Ya intenté obtener este comportamiento usando la opción joinedload de la siguiente manera:

Event.query.filter(Event.timestamp == day)
.join(Event.user)
.options(joinedload(Event.user))
.all()

También intenté establecer el atributo “lazy = ‘joined'” en la clase de entidad para la relación, lo que no tuvo ninguna diferencia.

La consulta SQL parece correcta y se genera correctamente la unión, pero si accedo al atributo del usuario más tarde, se envía otra consulta de unión. Con otras estrategias tampoco funciona, como la opción “contains_eager”.
Por lo tanto, el comportamiento esperado sería guardar toda la información en la primera carga y no ejecutar una consulta más tarde.

Todas las opciones de SQLAlchemy son las predeterminadas, excepto DATABASE_URI. ¿Hay alguna opción global para desactivar este comportamiento o para anular la predeterminada?

Las Entidades son las siguientes:

class Event(db.Model):
    __tablename__ = "event"
    __table_args__ = (
        db.ForeignKeyConstraint(
            ["username", "userfirstname"], ["users.name", "users.firstname"]
        ),
    )
    timestamp = db.Column(db.Date, primary_key=True, index=True)
    username = db.Column(db.String, primary_key=True)
    userfirstname = db.Column(db.String, primary_key=True)
    ...

    user = db.relationship("UserEntity")


class UserEntity(db.Model):
    __tablename__ = "users"
    name = db.Column(db.String, primary_key=True)
    firstname = db.Column(db.String, primary_key=True)
    ...
Tags:  ,

Answer

  1. Avatar for davy.ai

    Basándonos en la información y el código proporcionado, parece que ya has intentado los métodos apropiados para cargar ambos objetos con una consulta de unión única. Sin embargo, el hecho de que se envíe otra consulta de unión al acceder al objeto hijo más adelante sugiere que todavía se está utilizando la carga perezosa.

    Una solución que podrías probar es establecer el atributo lazy de la relación user en "joined" no solo en la definición de la clase entidad, sino también en la consulta misma. Esto se puede hacer usando la opción contains_eager de la siguiente manera:

    Event.query.filter(Event.timestamp == day)\
        .join(Event.user)\
        .options(contains_eager(Event.user, alias=UserEntity))\
        .all()
    

    Ten en cuenta que debes proporcionar un alias a la opción contains_eager para especificar la clase real de la entidad unida, que en este caso es UserEntity.

    Otra posible solución es utilizar subconsultas en lugar de uniones para recuperar todos los datos requeridos en una única consulta. Esto se puede lograr definiendo una subconsulta para la tabla secundaria y uniendo la tabla principal con ella utilizando la opción alias. Aquí tienes un ejemplo:

    subq = db.session.query(
        UserEntity.name, UserEntity.firstname # Añade otros campos si es necesario
    ).subquery()
    
    events = db.session.query(Event).\
        options(subqueryload(Event.user, subq))\
        .join(subq, Event.user)\
        .filter(Event.timestamp == day)\
        .all()
    

    Este enfoque debería cargar todos los datos en una única consulta y evitar cualquier consulta posterior al acceder al objeto hijo. Sin embargo, puede que no sea tan eficiente como el enfoque anterior, especialmente si tienes conjuntos de resultados grandes.

    En cuanto a si hay una opción global para desactivar por completo la carga perezosa, no conozco ninguna opción de este tipo en SQLAlchemy. Sin embargo, podrías considerar establecer el atributo lazy de todas tus relaciones en "joined" para asegurarte de que todos los objetos unidos se carguen con el objeto principal de forma predeterminada. Esto se puede hacer utilizando una clase base personalizada para tus modelos:

    class BaseModel(db.Model):
        __abstract__ = True
        __lazy_options__ = {"lazy": "joined"}
    
        id = db.Column(db.Integer, primary_key=True)
    
    
    class Event(BaseModel):
        __tablename__ = "event"
        __table_args__ = (
            db.ForeignKeyConstraint(
                ["username", "userfirstname"], ["users.name", "users.firstname"]
            ),
        )
        timestamp = db.Column(db.Date, index=True)
        username = db.Column(db.String)
        userfirstname = db.Column(db.String)
    
        user = db.relationship("UserEntity", backref="events")
    
    
    class UserEntity(BaseModel):
        __tablename__ = "users"
        name = db.Column(db.String)
        firstname = db.Column(db.String)
    

    Al definir una clase base personalizada con un atributo __lazy_options__ establecido en {"lazy": "joined"}, todas las relaciones declaradas en las subclases tendrán su atributo lazy establecido en "joined". Esto puede ahorrarte tener que especificar el atributo lazy en todas las declaraciones de relaciones. Sin embargo, ten en cuenta que esto puede no ser deseable en todos los casos, ya que puede llevar a consultas innecesariamente grandes para algunas relaciones.

Comments are closed.