9

我希望创建从另一个表填充的对象的映射属性。

使用 SQLAlchemy 文档示例,我希望在 Address 类上存在一个 user_name 字段,以便可以轻松查询和访问(无需第二次往返数据库)

例如,我希望能够查询和过滤,user_name Address.query.filter(Address.user_name == 'wcdolphin').first() 并且还可以访问user_name所有地址对象的属性,而不会降低性能,并让它像预期的属性一样正确地持久化写入__tablename__

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    addresses = relation("Address", backref="user")

class Address(Base):
    __tablename__ = 'addresses'

    id = Column(Integer, primary_key=True)
    email = Column(String(50))
    user_name = Column(Integer, ForeignKey('users.name'))#This line is wrong

我该怎么做呢?

我发现文档相对难以理解,因为它似乎不符合大多数示例,尤其是 Flask-SQLAlchemy 示例。

4

2 回答 2

7

您可以join在查询对象上使用 a 来执行此操作,无需直接指定此属性。所以你的模型看起来像:

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

Base = declarative_base()
engine = create_engine('sqlite:///')
Session = sessionmaker(bind=engine)

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    addresses = relation("Address", backref="user")

class Address(Base):
    __tablename__ = 'addresses'
    id = Column(Integer, primary_key=True)
    email = Column(String(50))
    user_id = Column(Integer, ForeignKey("users.id"))


Base.metadata.create_all(engine)

过滤用户名后的地址查询如下所示:

>>> session = Session()
>>> session.add(Address(user=User(name='test')))
>>> session.query(Address).join(User).filter(User.name == 'test').first()
<__main__.Address object at 0x02DB3730>

编辑:由于您可以从地址对象直接访问用户,因此无需直接将属性引用到 Address 类:

>>> a = session.query(Address).join(User).filter(User.name == 'test').first()
>>> a.user.name
'test'
于 2012-06-05T06:07:04.213 回答
3

如果您确实希望 Address 具有启用 SQL 的“User.name”版本而无需显式加入,则需要使用相关子查询。这适用于所有情况,但在数据库端(尤其是 MySQL)往往效率低下,因此与使用常规 JOIN 相比,SQL 端可能会降低性能。运行一些 EXPLAIN 测试可能有助于分析可能产生的影响。

相关 column_property() 的另一个示例位于http://docs.sqlalchemy.org/en/latest/orm/mapped_sql_expr.html#using-column-property

对于“设置”事件,相关子查询表示只读属性,但事件可用于拦截更改并将其应用于父用户行。下面介绍了两种方法,一种使用常规身份映射机制,如果用户行不存在,则会导致加载,另一种直接向行发出更新:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base= declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    addresses = relation("Address", backref="user")

class Address(Base):
    __tablename__ = 'addresses'

    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'))
    email = Column(String(50))

Address.user_name = column_property(select([User.name]).where(User.id==Address.id))

from sqlalchemy import event
@event.listens_for(Address.user_name, "set")
def _set_address_user_name(target, value, oldvalue, initiator):
    # use ORM identity map + flush
    target.user.name = value

    # use direct UPDATE
    #object_session(target).query(User).with_parent(target).update({'name':value})

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)
s.add_all([
    User(name='u1', addresses=[Address(email='e1'), Address(email='e2')])
])
s.commit()

a1 = s.query(Address).filter(Address.user_name=="u1").first()
assert a1.user_name == "u1"

a1.user_name = 'u2'
s.commit()

a1 = s.query(Address).filter(Address.user_name=="u2").first()
assert a1.user_name == "u2"
于 2012-06-12T22:30:01.413 回答