SqlAlchemy 2.0 中文文档(五十六)(2)https://developer.aliyun.com/article/1563155
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 查询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 查询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 查询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 查询与核心选择统一
概要
Query
对象(以及BakedQuery
和ShardedQuery
扩展)成为长期的遗留对象,被直接使用select()
构造与Session.execute()
方法取代。从Query
返回的对象,以列表形式返回对象或元组,或作为标量 ORM 对象统一地作为Session.execute()
返回的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()
构造适用于核心和 ORM 用例,当通过Session.execute()
方法调用时,将返回 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 的历史中的某个时期,作为用户建议的一个新的、“可构建”的查询对象被接受的。在之前的 SQLAlchemy 中,像 .where()
方法这样的概念,SelectResults
称为 .filter()
,是不存在的,而 select()
构造只使用了现在已经弃用的“一次全部”构造样式,该样式已在 select() no longer accepts varied constructor arguments, columns are passed positionally 中不再接受各种构造函数参数。
随着新方法的推出,该对象逐渐演变成为 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() 方法移到会话
概要
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
讨论
Query
对象在 2.0 中将成为传统对象,因为 ORM 查询现在可以使用select()
对象。由于Query.get()
方法与Session
定义了一种特殊的交互,并且甚至不一定会发出查询,因此更适合将其作为Session
的一部分,其中它类似于其他“身份”方法,例如refresh
和merge
。
SQLAlchemy 最初包含了 “get()” 来模仿 Hibernate 的 Session.load()
方法。就像经常发生的那样,我们稍微弄错了,因为这个方法实际上更多地与Session
有关,而不是编写 SQL 查询。
概要
这指的是诸如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 查询 - 使用属性列表进行链接的链式形式,而不是单独调用,已移除
概要
关于 ORM 查询 - 加入 / 加载关系使用属性,而不是字符串的方式将被移除:
# chaining removed q = session.query(User).join("orders", "items", "keywords")
迁移到 2.0
使用单独的调用Query.join()
进行 1.x /2.0 交叉兼容使用:
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()
。
SqlAlchemy 2.0 中文文档(五十六)(4)https://developer.aliyun.com/article/1563157