SqlAlchemy 2.0 中文文档(五十六)(7)https://developer.aliyun.com/article/1563160
2.0 迁移 - ORM 使用
SQLAlchemy 2.0 中最显著的可见变化是与Session.execute()
结合使用select()
来运行 ORM 查询,而不是使用Session.query()
。如其他地方所述,实际上没有计划删除Session.query()
API 本身,因为它现在是通过内部使用新 API 来实现的,它将作为遗留 API 保留,并且两个 API 都可以自由使用。
下表提供了一般调用形式的变化介绍,并链接到每个技术的文档。单个迁移说明在表格后面的嵌入部分中,可能包含未在此处概述的其他说明。
主要 ORM 查询模式概述
|
session.query(User).get(42)
|
session.get(User, 42)
ORM 查询 - get() 方法移至 Session |
|
session.query(User).all()
|
session.execute( select(User) ).scalars().all() # or session.scalars( select(User) ).all()
ORM 查询与 Core Select 统一Session.scalars() Result.scalars() |
|
session.query(User).\ filter_by(name="some user").\ one()
|
session.execute( select(User). filter_by(name="some user") ).scalar_one()
ORM 查询与 Core Select 统一Result.scalar_one() |
|
session.query(User).\ filter_by(name="some user").\ first()
|
session.scalars( select(User). filter_by(name="some user"). limit(1) ).first()
ORM 查询与 Core Select 统一Result.first() |
|
session.query(User).options( joinedload(User.addresses) ).all()
|
session.scalars( select(User). options( joinedload(User.addresses) ) ).unique().all()
|
session.query(User).\ join(Address).\ filter( Address.email == "e@sa.us" ).\ all()
|
session.execute( select(User). join(Address). where( Address.email == "e@sa.us" ) ).scalars().all()
|
session.query(User).\ from_statement( text("select * from users") ).\ all()
|
session.scalars( select(User). from_statement( text("select * from users") ) ).all()
|
session.query(User).\ join(User.addresses).\ options( contains_eager(User.addresses) ).\ populate_existing().all()
|
session.execute( select(User) .join(User.addresses) .options( contains_eager(User.addresses) ) .execution_options( populate_existing=True ) ).scalars().all()
|
session.query(User).\ filter(User.name == "foo").\ update( {"fullname": "Foo Bar"}, synchronize_session="evaluate" )
|
session.execute( update(User) .where(User.name == "foo") .values(fullname="Foo Bar") .execution_options( synchronize_session="evaluate" ) )
启用 ORM 的 INSERT、UPDATE 和 DELETE 语句 |
|
session.query(User).count()
|
session.scalar( select(func.count()). select_from(User) ) # or session.scalar( select(func.count(User.id)) )
ORM 查询与 Core Select 统一
概要
Query
对象(以及 BakedQuery
和 ShardedQuery
扩展)成为长期遗留对象,被直接使用 select()
构造与 Session.execute()
方法取代。从 Query
返回的对象形式为对象或元组的列表,或作为标量 ORM 对象返回的结果统一作为 Result
对象,其接口与 Core 执行一致。
以下是遗留代码示例:
session = Session(engine) # becomes legacy use case user = session.query(User).filter_by(name="some user").one() # becomes legacy use case user = session.query(User).filter_by(name="some user").first() # becomes legacy use case user = session.query(User).get(5) # becomes legacy use case for user in ( session.query(User).join(User.addresses).filter(Address.email == "some@email.com") ): ... # becomes legacy use case users = session.query(User).options(joinedload(User.addresses)).order_by(User.id).all() # becomes legacy use case users = session.query(User).from_statement(text("select * from users")).all() # etc
迁移至 2.0
因为 ORM 应用程序的绝大部分预期会使用 Query
对象,并且 Query
接口的可用性不会影响新接口,该对象将在 2.0 版本中保留,但将不再是文档的一部分,也大多不会得到支持。 select()
构造现在适用于 Core 和 ORM 用例,当通过 Session.execute()
方法调用时,将返回 ORM 导向的结果,也就是说,如果需要 ORM 对象,那么就返回 ORM 对象。
Select()
构造 添加了许多新方法,以与 Query
兼容,包括 Select.filter()
、Select.filter_by()
、重新设计的 Select.join()
和 Select.outerjoin()
方法,Select.options()
等。 Query
的其他补充方法,例如 Query.populate_existing()
,则通过执行选项来实现。
返回结果以 Result
对象形式呈现,这是 SQLAlchemy ResultProxy
对象的新版本,还添加了许多与 Query
兼容的新方法,包括 Result.one()
、Result.all()
、Result.first()
、Result.one_or_none()
等。
然而,Result
对象确实需要一些不同的调用模式,因为当首次返回时,它将始终返回元组,并且不会在内存中去重结果。为了像Query
那样返回单个 ORM 对象,必须首先调用Result.scalars()
修饰符。为了返回唯一的对象,当使用连接式急加载时是必要的,必须首先调用Result.unique()
修饰符。
包括执行选项等在内的所有新特性的select()
的文档在 ORM 查询指南中。
以下是迁移到select()
的一些示例:
session = Session(engine) user = session.execute(select(User).filter_by(name="some user")).scalar_one() # for first(), no LIMIT is applied automatically; add limit(1) if LIMIT # is desired on the query user = ( session.execute(select(User).filter_by(name="some user").limit(1)).scalars().first() ) # get() moves to the Session directly user = session.get(User, 5) for user in session.execute( select(User).join(User.addresses).filter(Address.email == "some@email.case") ).scalars(): ... # when using joinedload() against collections, use unique() on the result users = ( session.execute(select(User).options(joinedload(User.addresses)).order_by(User.id)) .unique() .all() ) # select() has ORM-ish methods like from_statement() that only work # if the statement is against ORM entities users = ( session.execute(select(User).from_statement(text("select * from users"))) .scalars() .all() )
讨论
SQLAlchemy 既有select()
构造,也有一个独立的Query
对象,其接口非常相似,但基本上是不兼容的,这可能是 SQLAlchemy 中最大的不一致性,这是随着时间的小增量添加而产生的,导致了两个不同的主要 API。
在 SQLAlchemy 的最初版本中,Query
对象根本不存在。最初的想法是Mapper
构造本身将能够选择行,并且Table
对象,而不是类,将用于以 Core 风格创建各种条件。Query
在 SQLAlchemy 的历史中的一些月份/年作为一个名为SelectResults
的新“可构建”查询对象的用户提案被接受。像.where()
方法这样的概念,SelectResults
称为.filter()
,在 SQLAlchemy 中以前不存在,并且select()
构造仅使用现在已弃用的“一次性”构造样式,该样式在 select()不再接受不同的构造参数,列按位置传递中。
随着新方法的推出,对象演变为 Query
对象,新增功能如能够选择单个列、能够一次选择多个实体、能够从 Query
对象而不是从 select
对象构建子查询。目标是使 Query
具有 select
的全部功能,可以完全组合构建 SELECT 语句,无需显式使用 select()
。同时,select()
也发展出了“生成”方法,如 Select.where()
和 Select.order_by()
。
在现代 SQLAlchemy 中,这一目标已经实现,这两个对象现在在功能上完全重叠。统一这些对象的主要挑战是,select()
对象需要保持与 ORM 完全无关。为了实现这一点,Query
中的绝大部分逻辑已经移至 SQL 编译阶段,其中 ORM 特定的编译器插件接收 Select
构造并根据 ORM 风格的查询内容解释其内容,然后传递给核心级别的编译器以创建 SQL 字符串。随着新的 SQL 编译缓存系统的出现,大部分这种 ORM 逻辑也被缓存。
另请参阅
ORM 查询现在与 select、update、delete 内部统一;2.0 风格执行可用 ### ORM 查询 - get() 方法移至 Session
简介
Query.get()
方法仍保留以供遗留目的,但主要接口现在是 Session.get()
方法:
# legacy usage user_obj = session.query(User).get(5)
迁移至 2.0
在 1.4 / 2.0 中,Session
对象添加了一个新的Session.get()
方法:
# 1.4 / 2.0 cross-compatible use user_obj = session.get(User, 5)
讨论
在 2.0 中,Query
对象将成为遗留对象,因为现在可以使用select()
对象进行 ORM 查询。由于Query.get()
方法与Session
定义了特殊的交互,并且甚至不一定会发出查询,因此更适合将其作为Session
的一部分,其中它类似于其他“标识”方法,例如refresh
和merge
。
SQLAlchemy 最初包含“get()”以类似于 Hibernate 的Session.load()
方法。就像经常发生的情况一样,我们稍微做错了,因为这种方法实际上更多地涉及Session
而不是编写 SQL 查询。### ORM 查询 - 使用属性而不是字符串进行关系连接/加载
概要
这指的是诸如Query.join()
之类的模式,以及查询选项,如joinedload()
,它目前接受字符串属性名称或实际类属性的混合。字符串形式将在 2.0 中全部移除:
# string use removed q = session.query(User).join("addresses") # string use removed q = session.query(User).options(joinedload("addresses")) # string use removed q = session.query(Address).filter(with_parent(u1, "addresses"))
升级到 2.0
现代 SQLAlchemy 1.x 版本支持推荐的技术,即使用映射属性:
# compatible with all modern SQLAlchemy versions q = session.query(User).join(User.addresses) q = session.query(User).options(joinedload(User.addresses)) q = session.query(Address).filter(with_parent(u1, User.addresses))
2.0 风格的使用也适用相同的技术:
# SQLAlchemy 1.4 / 2.0 cross compatible use stmt = select(User).join(User.addresses) result = session.execute(stmt) stmt = select(User).options(joinedload(User.addresses)) result = session.execute(stmt) stmt = select(Address).where(with_parent(u1, User.addresses)) result = session.execute(stmt)
讨论
字符串调用形式不明确,并且需要内部进行额外的工作以确定适当的路径并检索正确的映射属性。通过直接传递 ORM 映射属性,不仅可以提前传递必要的信息,而且该属性还是经过类型化的,并且更具有与 IDE 和 pep-484 集成的潜在兼容性。
ORM 查询 - 使用属性列表而不是单独调用进行链式调用已移除
概要
接受多个映射属性列表的“链接”形式和加载器选项将被移除:
# chaining removed q = session.query(User).join("orders", "items", "keywords")
升级到 2.0
对于 1.x / 2.0 跨版本兼容使用,使用单独的调用Query.join()
:
q = session.query(User).join(User.orders).join(Order.items).join(Item.keywords)
对于 2.0 风格 使用,Select
具有与 Select.join()
相同的行为,还具有一个新的 Select.join_from()
方法,允许显式左侧:
# 1.4 / 2.0 cross compatible stmt = select(User).join(User.orders).join(Order.items).join(Item.keywords) result = session.execute(stmt) # join_from can also be helpful stmt = select(User).join_from(User, Order).join_from(Order, Item, Order.items) result = session.execute(stmt)
讨论
移除属性的链接符合简化方法的调用接口,例如 Select.join()
。
ORM 查询 - join(…, aliased=True),移除了 from_joinpoint
简介
Query.join()
上的 aliased=True
选项被移除,from_joinpoint
标志也被移除:
# no longer supported q = ( session.query(Node) .join("children", aliased=True) .filter(Node.name == "some sub child") .join("children", from_joinpoint=True, aliased=True) .filter(Node.name == "some sub sub child") )
迁移到 2.0 版本
使用显式别名代替:
n1 = aliased(Node) n2 = aliased(Node) q = ( select(Node) .join(Node.children.of_type(n1)) .where(n1.name == "some sub child") .join(n1.children.of_type(n2)) .where(n2.name == "some sub child") )
讨论
Query.join()
上的 aliased=True
选项是另一个几乎从未被使用过的功能,根据广泛的代码搜索来查找实际使用这个功能的情况。aliased=True
标志需要的内部复杂性是巨大的,并且将在 2.0 版本中被移除。
大多数用户不熟悉这个标志,但它允许沿着连接自动对元素进行别名,然后将自动别名应用于过滤条件。最初的用例是帮助长链的自引用连接,就像上面显示的例子一样。然而,过滤条件的自动调整在内部是极其复杂的,并且在现实世界的应用中几乎从不使用。这种模式还会导致问题,例如,如果在链中的每个链接中需要添加过滤条件;那么模式必须使用 from_joinpoint
标志,而 SQLAlchemy 开发人员绝对找不到此参数在现实世界的应用中的任何使用情况。
aliased=True
和 from_joinpoint
参数是在Query
对象在关系属性上还没有很好的加入能力时开发的,像PropComparator.of_type()
这样的函数还不存在,而aliased()
构造本身在早期也不存在。### 使用 DISTINCT 与其他列,但仅选择实体
简介
当使用 distinct 时,Query
将自动添加 ORDER BY 中的列。以下查询将从所有 User 列以及“address.email_address”中选择,但只返回 User 对象:
# 1.xx code result = ( session.query(User) .join(User.addresses) .distinct() .order_by(Address.email_address) .all() )
在 2.0 版本中,“email_address”列不会自动添加到列子句中,上述查询将失败,因为关系数据库在使用 DISTINCT 时不允许您按“address.email_address”排序,如果它也不在列子句中。
迁移到 2.0
在 2.0 中,必须显式添加列。要解决仅返回主实体对象而不是额外列的问题,请使用Result.columns()
方法:
# 1.4 / 2.0 code stmt = ( select(User, Address.email_address) .join(User.addresses) .distinct() .order_by(Address.email_address) ) result = session.execute(stmt).columns(User).all()
讨论
这种情况是Query
有限的灵活性的一个例子,导致需要添加隐式的“神奇”行为;“email_address”列被隐式添加到列子句中,然后额外的内部逻辑将从实际返回的结果中省略该列。
新方法简化了交互,并明确了正在发生的事情,同时仍然可以实现原始用例而不会造成不便。 ### 从查询本身选择作为子查询,例如“from_self()”
概要
Query.from_self()
方法将从Query
中移除:
# from_self is removed q = ( session.query(User, Address.email_address) .join(User.addresses) .from_self(User) .order_by(Address.email_address) )
迁移到 2.0
aliased()
构造可以用于根据任意可选择的实体发出 ORM 查询。在 1.4 版本中已增强了对同一子查询多次使用不同实体的支持。这可以在 1.x 风格中与Query
一起使用,如下所示;请注意,由于最终查询想要根据User
和Address
实体查询,因此创建了两个单独的aliased()
构造:
from sqlalchemy.orm import aliased subq = session.query(User, Address.email_address).join(User.addresses).subquery() ua = aliased(User, subq) aa = aliased(Address, subq) q = session.query(ua, aa).order_by(aa.email_address)
相同的形式可以在 2.0 风格中使用:
from sqlalchemy.orm import aliased subq = select(User, Address.email_address).join(User.addresses).subquery() ua = aliased(User, subq) aa = aliased(Address, subq) stmt = select(ua, aa).order_by(aa.email_address) result = session.execute(stmt)
讨论
Query.from_self()
方法是一个非常复杂的方法,很少被使用。该方法的目的是将 Query
转换为子查询,然后返回一个从该子查询中 SELECT 的新 Query
。该方法的复杂之处在于返回的查询会自动将 ORM 实体和列转换为子查询中的 SELECT,同时允许修改要 SELECT 的实体和列。
因为 Query.from_self()
在生成的 SQL 中隐含了大量的转换,虽然它确实允许以非常简洁的方式执行某种模式,但这种方法在实际应用中很少见,因为它不容易理解。
新方法利用 aliased()
构造,使得 ORM 内部不需要猜测哪些实体和列应该以何种方式适应;在上面的示例中,ua
和 aa
对象都是 AliasedClass
实例,为内部提供了一个明确的标记,指示子查询应该被引用以及对于查询的给定组件考虑了哪个实体列或关系。
SQLAlchemy 1.4 还提供了一种改进的标记风格,不再需要使用包含表名以消除同名列的长标签。在上面的示例中,即使我们的 User
和 Address
实体具有重叠的列名,我们也可以同时从两个实体中选择而无需指定任何特定的标签:
# 1.4 / 2.0 code subq = select(User, Address).join(User.addresses).subquery() ua = aliased(User, subq) aa = aliased(Address, subq) stmt = select(ua, aa).order_by(aa.email_address) result = session.execute(stmt)
上述查询将澄清 User
和 Address
的 .id
列,其中 Address.id
被呈现和跟踪为 id_1
:
SELECT anon_1.id AS anon_1_id, anon_1.id_1 AS anon_1_id_1, anon_1.user_id AS anon_1_user_id, anon_1.email_address AS anon_1_email_address FROM ( SELECT "user".id AS id, address.id AS id_1, address.user_id AS user_id, address.email_address AS email_address FROM "user" JOIN address ON "user".id = address.user_id ) AS anon_1 ORDER BY anon_1.email_address
从其他可选择的实体中选择实体;Query.select_entity_from()
概要
Query.select_entity_from()
方法将在 2.0 中移除:
subquery = session.query(User).filter(User.id == 5).subquery() user = session.query(User).select_entity_from(subquery).first()
迁移到 2.0
正如在 从查询本身作为子查询进行选择,例如“from_self()” 中所描述的那样,aliased()
对象提供了一个单一的地方,可以实现诸如“从子查询选择实体”之类的操作。使用 1.x 风格:
from sqlalchemy.orm import aliased subquery = session.query(User).filter(User.name.like("%somename%")).subquery() ua = aliased(User, subquery) user = session.query(ua).order_by(ua.id).first()
使用 2.0 风格:
from sqlalchemy.orm import aliased subquery = select(User).where(User.name.like("%somename%")).subquery() ua = aliased(User, subquery) # note that LIMIT 1 is not automatically supplied, if needed user = session.execute(select(ua).order_by(ua.id).limit(1)).scalars().first()
讨论
这里的要点基本与从查询本身选择子查询,例如“from_self()”讨论的内容相同。 Query.select_from_entity()
方法是指示查询从替代可选择项加载特定 ORM 映射实体的行的另一种方法,这涉及在以后的查询中,例如在 WHERE 子句或 ORDER BY 中,ORM 将自动为该实体应用别名,如Query.from_self()
的情况一样,当使用显式aliased()
对象时,更容易跟踪发生的情况,无论从用户的角度还是从 SQLAlchemy ORM 内部处理的角度。
SqlAlchemy 2.0 中文文档(五十六)(9)https://developer.aliyun.com/article/1563162