[{"data":1,"prerenderedAt":1185},["ShallowReactive",2],{"page-\u002Fmastering-sqlalchemy-20-core-and-orm-architecture\u002Fcore-vs-orm-architecture-decisions\u002F":3},{"id":4,"title":5,"body":6,"description":58,"extension":1179,"meta":1180,"navigation":124,"path":1181,"seo":1182,"stem":1183,"__hash__":1184},"content\u002Fmastering-sqlalchemy-20-core-and-orm-architecture\u002Fcore-vs-orm-architecture-decisions\u002Findex.md","Core vs ORM Architecture Decisions in SQLAlchemy 2.0",{"type":7,"value":8,"toc":1170},"minimark",[9,13,18,22,52,496,500,515,538,699,703,718,734,738,749,756,784,788,802,805,1030,1034,1098,1102,1108,1127,1143,1166],[10,11,5],"h1",{"id":12},"core-vs-orm-architecture-decisions-in-sqlalchemy-20",[14,15,17],"h2",{"id":16},"_1-architectural-foundations-unified-execution-model","1. Architectural Foundations: Unified Execution Model",[19,20,21],"p",{},"SQLAlchemy 2.0 resolves the historical dichotomy between the SQL expression language (Core) and the object-relational mapping layer (ORM) by consolidating both under a single, unified execution model. Core operates as a stateless SQL compilation engine, translating Pythonic expression trees directly into parameterized SQL. The ORM builds atop this foundation, introducing a Unit-of-Work pattern, an identity map, and automatic entity hydration. For architects designing high-throughput systems, understanding this boundary is critical: Core excels in stateless data pipelines and bulk operations, while the ORM provides the domain-driven abstractions necessary for complex transactional service layers.",[19,23,24,25,29,30,33,34,37,38,41,42,45,46,51],{},"In 2.0, the legacy ",[26,27,28],"code",{},"Query"," API is deprecated in favor of a single ",[26,31,32],{},"select()"," construct that compiles identically regardless of execution context. This architectural unification means that query construction, filtering, and joining syntax are now framework-agnostic. The divergence occurs strictly at the execution boundary: ",[26,35,36],{},"Connection.execute()"," returns raw ",[26,39,40],{},"Row"," objects, while ",[26,43,44],{},"Session.execute()"," hydrates mapped entities and tracks state. For a comprehensive breakdown of this unified execution model, refer to ",[47,48,50],"a",{"href":49},"\u002Fmastering-sqlalchemy-20-core-and-orm-architecture\u002F","Mastering SQLAlchemy 2.0 Core and ORM Architecture",".",[53,54,59],"pre",{"className":55,"code":56,"language":57,"meta":58,"style":58},"language-python shiki shiki-themes github-light github-dark","from typing import Sequence\nfrom sqlalchemy import select, Column, Integer, String, Table, MetaData\nfrom sqlalchemy.ext.asyncio import AsyncSession, AsyncConnection, create_async_engine\nfrom sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase\n\n# Core Table Definition\nmetadata = MetaData()\nusers_core = Table(\n \"users\", metadata,\n Column(\"id\", Integer, primary_key=True),\n Column(\"email\", String(255), nullable=False)\n)\n\n# ORM Model Definition\nclass UserORM(DeclarativeBase):\n __tablename__ = \"users\"\n id: Mapped[int] = mapped_column(primary_key=True)\n email: Mapped[str] = mapped_column(String(255), nullable=False)\n\nasync def demonstrate_unified_select(\n conn: AsyncConnection, \n session: AsyncSession\n) -> None:\n # Identical 2.0 select() construct\n stmt = select(users_core.c.id, users_core.c.email).where(\n users_core.c.email.like(\"%@example.com\")\n )\n\n # Core Execution: Returns Row objects, zero identity map overhead\n core_result = await conn.execute(stmt)\n core_rows: Sequence[tuple[int, str]] = core_result.fetchall()\n\n # ORM Execution: Returns mapped entities, triggers identity map lookup\n orm_result = await session.execute(\n select(UserORM).where(UserORM.email.like(\"%@example.com\"))\n )\n orm_entities: Sequence[UserORM] = orm_result.scalars().all()\n","python","",[26,60,61,80,93,106,119,126,133,145,156,166,191,219,224,229,235,254,265,293,321,326,341,347,353,365,371,382,393,399,404,410,424,445,450,456,469,480,485],{"__ignoreMap":58},[62,63,66,70,74,77],"span",{"class":64,"line":65},"line",1,[62,67,69],{"class":68},"szBVR","from",[62,71,73],{"class":72},"sVt8B"," typing ",[62,75,76],{"class":68},"import",[62,78,79],{"class":72}," Sequence\n",[62,81,83,85,88,90],{"class":64,"line":82},2,[62,84,69],{"class":68},[62,86,87],{"class":72}," sqlalchemy ",[62,89,76],{"class":68},[62,91,92],{"class":72}," select, Column, Integer, String, Table, MetaData\n",[62,94,96,98,101,103],{"class":64,"line":95},3,[62,97,69],{"class":68},[62,99,100],{"class":72}," sqlalchemy.ext.asyncio ",[62,102,76],{"class":68},[62,104,105],{"class":72}," AsyncSession, AsyncConnection, create_async_engine\n",[62,107,109,111,114,116],{"class":64,"line":108},4,[62,110,69],{"class":68},[62,112,113],{"class":72}," sqlalchemy.orm ",[62,115,76],{"class":68},[62,117,118],{"class":72}," Mapped, mapped_column, DeclarativeBase\n",[62,120,122],{"class":64,"line":121},5,[62,123,125],{"emptyLinePlaceholder":124},true,"\n",[62,127,129],{"class":64,"line":128},6,[62,130,132],{"class":131},"sJ8bj","# Core Table Definition\n",[62,134,136,139,142],{"class":64,"line":135},7,[62,137,138],{"class":72},"metadata ",[62,140,141],{"class":68},"=",[62,143,144],{"class":72}," MetaData()\n",[62,146,148,151,153],{"class":64,"line":147},8,[62,149,150],{"class":72},"users_core ",[62,152,141],{"class":68},[62,154,155],{"class":72}," Table(\n",[62,157,159,163],{"class":64,"line":158},9,[62,160,162],{"class":161},"sZZnC"," \"users\"",[62,164,165],{"class":72},", metadata,\n",[62,167,169,172,175,178,182,184,188],{"class":64,"line":168},10,[62,170,171],{"class":72}," Column(",[62,173,174],{"class":161},"\"id\"",[62,176,177],{"class":72},", Integer, ",[62,179,181],{"class":180},"s4XuR","primary_key",[62,183,141],{"class":68},[62,185,187],{"class":186},"sj4cs","True",[62,189,190],{"class":72},"),\n",[62,192,194,196,199,202,205,208,211,213,216],{"class":64,"line":193},11,[62,195,171],{"class":72},[62,197,198],{"class":161},"\"email\"",[62,200,201],{"class":72},", String(",[62,203,204],{"class":186},"255",[62,206,207],{"class":72},"), ",[62,209,210],{"class":180},"nullable",[62,212,141],{"class":68},[62,214,215],{"class":186},"False",[62,217,218],{"class":72},")\n",[62,220,222],{"class":64,"line":221},12,[62,223,218],{"class":72},[62,225,227],{"class":64,"line":226},13,[62,228,125],{"emptyLinePlaceholder":124},[62,230,232],{"class":64,"line":231},14,[62,233,234],{"class":131},"# ORM Model Definition\n",[62,236,238,241,245,248,251],{"class":64,"line":237},15,[62,239,240],{"class":68},"class",[62,242,244],{"class":243},"sScJk"," UserORM",[62,246,247],{"class":72},"(",[62,249,250],{"class":243},"DeclarativeBase",[62,252,253],{"class":72},"):\n",[62,255,257,260,262],{"class":64,"line":256},16,[62,258,259],{"class":72}," __tablename__ ",[62,261,141],{"class":68},[62,263,264],{"class":161}," \"users\"\n",[62,266,268,271,274,277,280,282,285,287,289,291],{"class":64,"line":267},17,[62,269,270],{"class":186}," id",[62,272,273],{"class":72},": Mapped[",[62,275,276],{"class":186},"int",[62,278,279],{"class":72},"] ",[62,281,141],{"class":68},[62,283,284],{"class":72}," mapped_column(",[62,286,181],{"class":180},[62,288,141],{"class":68},[62,290,187],{"class":186},[62,292,218],{"class":72},[62,294,296,299,302,304,306,309,311,313,315,317,319],{"class":64,"line":295},18,[62,297,298],{"class":72}," email: Mapped[",[62,300,301],{"class":186},"str",[62,303,279],{"class":72},[62,305,141],{"class":68},[62,307,308],{"class":72}," mapped_column(String(",[62,310,204],{"class":186},[62,312,207],{"class":72},[62,314,210],{"class":180},[62,316,141],{"class":68},[62,318,215],{"class":186},[62,320,218],{"class":72},[62,322,324],{"class":64,"line":323},19,[62,325,125],{"emptyLinePlaceholder":124},[62,327,329,332,335,338],{"class":64,"line":328},20,[62,330,331],{"class":68},"async",[62,333,334],{"class":68}," def",[62,336,337],{"class":243}," demonstrate_unified_select",[62,339,340],{"class":72},"(\n",[62,342,344],{"class":64,"line":343},21,[62,345,346],{"class":72}," conn: AsyncConnection, \n",[62,348,350],{"class":64,"line":349},22,[62,351,352],{"class":72}," session: AsyncSession\n",[62,354,356,359,362],{"class":64,"line":355},23,[62,357,358],{"class":72},") -> ",[62,360,361],{"class":186},"None",[62,363,364],{"class":72},":\n",[62,366,368],{"class":64,"line":367},24,[62,369,370],{"class":131}," # Identical 2.0 select() construct\n",[62,372,374,377,379],{"class":64,"line":373},25,[62,375,376],{"class":72}," stmt ",[62,378,141],{"class":68},[62,380,381],{"class":72}," select(users_core.c.id, users_core.c.email).where(\n",[62,383,385,388,391],{"class":64,"line":384},26,[62,386,387],{"class":72}," users_core.c.email.like(",[62,389,390],{"class":161},"\"%@example.com\"",[62,392,218],{"class":72},[62,394,396],{"class":64,"line":395},27,[62,397,398],{"class":72}," )\n",[62,400,402],{"class":64,"line":401},28,[62,403,125],{"emptyLinePlaceholder":124},[62,405,407],{"class":64,"line":406},29,[62,408,409],{"class":131}," # Core Execution: Returns Row objects, zero identity map overhead\n",[62,411,413,416,418,421],{"class":64,"line":412},30,[62,414,415],{"class":72}," core_result ",[62,417,141],{"class":68},[62,419,420],{"class":68}," await",[62,422,423],{"class":72}," conn.execute(stmt)\n",[62,425,427,430,432,435,437,440,442],{"class":64,"line":426},31,[62,428,429],{"class":72}," core_rows: Sequence[tuple[",[62,431,276],{"class":186},[62,433,434],{"class":72},", ",[62,436,301],{"class":186},[62,438,439],{"class":72},"]] ",[62,441,141],{"class":68},[62,443,444],{"class":72}," core_result.fetchall()\n",[62,446,448],{"class":64,"line":447},32,[62,449,125],{"emptyLinePlaceholder":124},[62,451,453],{"class":64,"line":452},33,[62,454,455],{"class":131}," # ORM Execution: Returns mapped entities, triggers identity map lookup\n",[62,457,459,462,464,466],{"class":64,"line":458},34,[62,460,461],{"class":72}," orm_result ",[62,463,141],{"class":68},[62,465,420],{"class":68},[62,467,468],{"class":72}," session.execute(\n",[62,470,472,475,477],{"class":64,"line":471},35,[62,473,474],{"class":72}," select(UserORM).where(UserORM.email.like(",[62,476,390],{"class":161},[62,478,479],{"class":72},"))\n",[62,481,483],{"class":64,"line":482},36,[62,484,398],{"class":72},[62,486,488,491,493],{"class":64,"line":487},37,[62,489,490],{"class":72}," orm_entities: Sequence[UserORM] ",[62,492,141],{"class":68},[62,494,495],{"class":72}," orm_result.scalars().all()\n",[14,497,499],{"id":498},"_2-query-construction-async-execution-patterns","2. Query Construction & Async Execution Patterns",[19,501,502,503,506,507,510,511,51],{},"The transition to 2.0 mandates a shift from the legacy ",[26,504,505],{},"session.query(Model).filter()"," pattern to the declarative ",[26,508,509],{},"select(Model).where()"," syntax. This change aligns Core and ORM query construction, enabling static type checkers to validate SQL expressions before runtime. When migrating legacy codebases, engineers must replace implicit session-bound queries with explicit statement objects. Detailed syntax migration patterns are documented in ",[47,512,514],{"href":513},"\u002Fmastering-sqlalchemy-20-core-and-orm-architecture\u002Fcore-vs-orm-architecture-decisions\u002Fhow-to-replace-queryfilter-with-selectwhere-in-sqlalchemy-20\u002F","How to Replace Query.filter with select.where in SQLAlchemy 2.0",[19,516,517,518,521,522,525,526,529,530,533,534,537],{},"Async execution introduces strict boundaries around the event loop. Core utilizes ",[26,519,520],{},"AsyncConnection"," for direct driver communication, while the ORM wraps connections in ",[26,523,524],{},"AsyncSession"," to manage state across ",[26,527,528],{},"await"," points. Driver selection heavily influences performance: ",[26,531,532],{},"asyncpg"," (PostgreSQL) provides native prepared statement caching and binary protocol support, whereas ",[26,535,536],{},"aiosqlite"," relies on thread-pool offloading due to SQLite's synchronous C API. For high-throughput data engineering workloads, bypassing the ORM identity map via Core's streaming execution prevents memory exhaustion.",[53,539,541],{"className":55,"code":540,"language":57,"meta":58,"style":58},"from sqlalchemy import insert\nfrom sqlalchemy.ext.asyncio import AsyncConnection\n\nasync def bulk_insert_via_core(conn: AsyncConnection, records: list[dict]) -> None:\n \"\"\"\n High-throughput async bulk insert bypassing ORM identity map.\n Uses executemany under the hood with asyncpg\u002Faiosqlite optimizations.\n \"\"\"\n stmt = insert(users_core).values(records)\n \n # stream_results=True enables cursor-based fetching for massive datasets\n # without loading entire result sets into application memory\n async with conn.begin():\n result = await conn.execute(stmt)\n await conn.commit()\n \n # In production, pair with executemany() or RETURNING for batch acknowledgment\n print(f\"Inserted {result.rowcount} rows via Core async pipeline\")\n",[26,542,543,554,565,569,591,596,601,606,610,619,624,629,634,645,656,663,667,672],{"__ignoreMap":58},[62,544,545,547,549,551],{"class":64,"line":65},[62,546,69],{"class":68},[62,548,87],{"class":72},[62,550,76],{"class":68},[62,552,553],{"class":72}," insert\n",[62,555,556,558,560,562],{"class":64,"line":82},[62,557,69],{"class":68},[62,559,100],{"class":72},[62,561,76],{"class":68},[62,563,564],{"class":72}," AsyncConnection\n",[62,566,567],{"class":64,"line":95},[62,568,125],{"emptyLinePlaceholder":124},[62,570,571,573,575,578,581,584,587,589],{"class":64,"line":108},[62,572,331],{"class":68},[62,574,334],{"class":68},[62,576,577],{"class":243}," bulk_insert_via_core",[62,579,580],{"class":72},"(conn: AsyncConnection, records: list[",[62,582,583],{"class":186},"dict",[62,585,586],{"class":72},"]) -> ",[62,588,361],{"class":186},[62,590,364],{"class":72},[62,592,593],{"class":64,"line":121},[62,594,595],{"class":161}," \"\"\"\n",[62,597,598],{"class":64,"line":128},[62,599,600],{"class":161}," High-throughput async bulk insert bypassing ORM identity map.\n",[62,602,603],{"class":64,"line":135},[62,604,605],{"class":161}," Uses executemany under the hood with asyncpg\u002Faiosqlite optimizations.\n",[62,607,608],{"class":64,"line":147},[62,609,595],{"class":161},[62,611,612,614,616],{"class":64,"line":158},[62,613,376],{"class":72},[62,615,141],{"class":68},[62,617,618],{"class":72}," insert(users_core).values(records)\n",[62,620,621],{"class":64,"line":168},[62,622,623],{"class":72}," \n",[62,625,626],{"class":64,"line":193},[62,627,628],{"class":131}," # stream_results=True enables cursor-based fetching for massive datasets\n",[62,630,631],{"class":64,"line":221},[62,632,633],{"class":131}," # without loading entire result sets into application memory\n",[62,635,636,639,642],{"class":64,"line":226},[62,637,638],{"class":68}," async",[62,640,641],{"class":68}," with",[62,643,644],{"class":72}," conn.begin():\n",[62,646,647,650,652,654],{"class":64,"line":231},[62,648,649],{"class":72}," result ",[62,651,141],{"class":68},[62,653,420],{"class":68},[62,655,423],{"class":72},[62,657,658,660],{"class":64,"line":237},[62,659,420],{"class":68},[62,661,662],{"class":72}," conn.commit()\n",[62,664,665],{"class":64,"line":256},[62,666,623],{"class":72},[62,668,669],{"class":64,"line":267},[62,670,671],{"class":131}," # In production, pair with executemany() or RETURNING for batch acknowledgment\n",[62,673,674,677,679,682,685,688,691,694,697],{"class":64,"line":295},[62,675,676],{"class":186}," print",[62,678,247],{"class":72},[62,680,681],{"class":68},"f",[62,683,684],{"class":161},"\"Inserted ",[62,686,687],{"class":186},"{",[62,689,690],{"class":72},"result.rowcount",[62,692,693],{"class":186},"}",[62,695,696],{"class":161}," rows via Core async pipeline\"",[62,698,218],{"class":72},[14,700,702],{"id":701},"_3-state-management-session-boundaries","3. State Management & Session Boundaries",[19,704,705,706,709,710,713,714,717],{},"The ORM's identity map guarantees object identity within a transactional scope but introduces measurable latency during change tracking and flush operations. Every attribute mutation is intercepted, and ",[26,707,708],{},"session.commit()"," triggers a full dirty-checking sweep. In contrast, Core executes stateless SQL with zero tracking overhead. For request-scoped web applications, the ORM's unit-of-work pattern aligns naturally with the request\u002Fresponse lifecycle. However, long-running background workers or event-driven consumers should explicitly configure ",[26,711,712],{},"expire_on_commit=False"," to prevent ",[26,715,716],{},"DetachedInstanceError"," when lazy-loaded relationships are accessed after the transaction boundary.",[19,719,720,721,725,726,729,730,733],{},"Session scoping dictates memory footprint and connection pool behavior. Request-scoped sessions should be tightly bound to the HTTP lifecycle, while worker processes require explicit transaction boundaries and connection recycling. Comprehensive strategies for managing these boundaries are outlined in ",[47,722,724],{"href":723},"\u002Fmastering-sqlalchemy-20-core-and-orm-architecture\u002Fsession-lifecycle-and-scope-management\u002F","Session Lifecycle and Scope Management",". When configuring async pools, ",[26,727,728],{},"pool_pre_ping=True"," and ",[26,731,732],{},"pool_recycle=3600"," are mandatory to prevent stale connection drops under high concurrency.",[14,735,737],{"id":736},"_4-advanced-schema-routing-type-extensions","4. Advanced Schema Routing & Type Extensions",[19,739,740,741,744,745,748],{},"Dynamic DDL generation, runtime schema switching, and multi-tenant routing are domains where Core provides superior flexibility. The ORM's declarative base assumes static table mappings, making runtime schema resolution cumbersome. Core's ",[26,742,743],{},"Table"," objects can be dynamically instantiated with varying ",[26,746,747],{},"schema"," parameters, enabling tenant isolation without model duplication. Implementation strategies for partitioning tenant data at the schema level are detailed in Implementing Multi-Tenant Schema Isolation in Core.",[19,750,751,752,755],{},"Beyond schema routing, type system extensions bridge the gap between database-native formats and Python domain types. SQLAlchemy's ",[26,753,754],{},"TypeDecorator"," enables transparent serialization for JSON, UUID, and encrypted payloads. When combined with community extensions, developers gain production-ready implementations for choice enums, password hashing, and geographic types. Integration patterns for these utilities are covered in Using SQLAlchemy 2.0 with SQLAlchemy-Utils Extensions.",[19,757,758,762,763,434,766,769,770,772,773,776,777,779,780,783],{},[759,760,761],"strong",{},"Critical Warning:"," Applying ORM relationship loaders (",[26,764,765],{},"selectinload",[26,767,768],{},"joinedload",") to Core ",[26,771,32],{}," statements results in silent query failures or ",[26,774,775],{},"ArgumentError"," exceptions. Loaders are strictly bound to the ORM's state management layer and must never be attached to raw ",[26,778,743],{}," or ",[26,781,782],{},"Column"," constructs.",[14,785,787],{"id":786},"_5-hybrid-architectures-migration-strategies","5. Hybrid Architectures & Migration Strategies",[19,789,790,791,794,795,797,798,51],{},"Enterprise systems rarely operate exclusively in Core or ORM. The most resilient architecture employs a hybrid execution model: routing bulk ETL, reporting aggregations, and dynamic schema queries through Core, while retaining the ORM for transactional domain logic, relationship traversal, and complex business rules. Incremental migration from 1.4 legacy patterns requires careful deprecation handling, particularly around ",[26,792,793],{},"future=True"," engine flags and legacy ",[26,796,28],{}," object removal. A step-by-step migration guide is available at ",[47,799,801],{"href":800},"\u002Fmastering-sqlalchemy-20-core-and-orm-architecture\u002Fmigrating-legacy-14-code-to-20-syntax\u002F","Migrating Legacy 1.4 Code to 2.0 Syntax",[19,803,804],{},"Performance trade-offs in hybrid architectures are quantifiable. ORM session flush latency scales linearly with tracked entity count, typically introducing 15–40ms overhead per 1,000 entities. Core execution maintains sub-5ms latency regardless of dataset size. For high-throughput async workloads, memory footprint benchmarks consistently show Core consuming 60–80% less heap space than equivalent ORM hydration cycles.",[53,806,808],{"className":55,"code":807,"language":57,"meta":58,"style":58},"from contextlib import asynccontextmanager\nfrom typing import AsyncGenerator\nfrom sqlalchemy.ext.asyncio import AsyncSession, AsyncConnection, async_sessionmaker\n\n@asynccontextmanager\nasync def hybrid_transaction_scope(\n session_factory: async_sessionmaker[AsyncSession]\n) -> AsyncGenerator[tuple[AsyncSession, AsyncConnection], None]:\n \"\"\"\n Yields both AsyncSession (ORM) and AsyncConnection (Core) \n within a single explicit async transaction scope.\n Ensures atomicity across hybrid execution boundaries.\n \"\"\"\n async with session_factory() as session:\n # Access underlying connection from the ORM session\n conn: AsyncConnection = session.connection()\n \n async with conn.begin():\n try:\n yield session, conn\n await conn.commit()\n except Exception:\n await conn.rollback()\n raise\n finally:\n await session.close()\n\n# Usage:\n# async with hybrid_transaction_scope(session_factory) as (session, conn):\n# await session.add(domain_entity) # ORM operation\n# await conn.execute(bulk_etl_stmt) # Core operation\n# # Both share the same transactional boundary\n",[26,809,810,822,833,844,848,853,864,869,879,883,888,893,898,902,917,922,932,936,944,951,959,965,975,982,987,994,1001,1005,1010,1015,1020,1025],{"__ignoreMap":58},[62,811,812,814,817,819],{"class":64,"line":65},[62,813,69],{"class":68},[62,815,816],{"class":72}," contextlib ",[62,818,76],{"class":68},[62,820,821],{"class":72}," asynccontextmanager\n",[62,823,824,826,828,830],{"class":64,"line":82},[62,825,69],{"class":68},[62,827,73],{"class":72},[62,829,76],{"class":68},[62,831,832],{"class":72}," AsyncGenerator\n",[62,834,835,837,839,841],{"class":64,"line":95},[62,836,69],{"class":68},[62,838,100],{"class":72},[62,840,76],{"class":68},[62,842,843],{"class":72}," AsyncSession, AsyncConnection, async_sessionmaker\n",[62,845,846],{"class":64,"line":108},[62,847,125],{"emptyLinePlaceholder":124},[62,849,850],{"class":64,"line":121},[62,851,852],{"class":243},"@asynccontextmanager\n",[62,854,855,857,859,862],{"class":64,"line":128},[62,856,331],{"class":68},[62,858,334],{"class":68},[62,860,861],{"class":243}," hybrid_transaction_scope",[62,863,340],{"class":72},[62,865,866],{"class":64,"line":135},[62,867,868],{"class":72}," session_factory: async_sessionmaker[AsyncSession]\n",[62,870,871,874,876],{"class":64,"line":147},[62,872,873],{"class":72},") -> AsyncGenerator[tuple[AsyncSession, AsyncConnection], ",[62,875,361],{"class":186},[62,877,878],{"class":72},"]:\n",[62,880,881],{"class":64,"line":158},[62,882,595],{"class":161},[62,884,885],{"class":64,"line":168},[62,886,887],{"class":161}," Yields both AsyncSession (ORM) and AsyncConnection (Core) \n",[62,889,890],{"class":64,"line":193},[62,891,892],{"class":161}," within a single explicit async transaction scope.\n",[62,894,895],{"class":64,"line":221},[62,896,897],{"class":161}," Ensures atomicity across hybrid execution boundaries.\n",[62,899,900],{"class":64,"line":226},[62,901,595],{"class":161},[62,903,904,906,908,911,914],{"class":64,"line":231},[62,905,638],{"class":68},[62,907,641],{"class":68},[62,909,910],{"class":72}," session_factory() ",[62,912,913],{"class":68},"as",[62,915,916],{"class":72}," session:\n",[62,918,919],{"class":64,"line":237},[62,920,921],{"class":131}," # Access underlying connection from the ORM session\n",[62,923,924,927,929],{"class":64,"line":256},[62,925,926],{"class":72}," conn: AsyncConnection ",[62,928,141],{"class":68},[62,930,931],{"class":72}," session.connection()\n",[62,933,934],{"class":64,"line":267},[62,935,623],{"class":72},[62,937,938,940,942],{"class":64,"line":295},[62,939,638],{"class":68},[62,941,641],{"class":68},[62,943,644],{"class":72},[62,945,946,949],{"class":64,"line":323},[62,947,948],{"class":68}," try",[62,950,364],{"class":72},[62,952,953,956],{"class":64,"line":328},[62,954,955],{"class":68}," yield",[62,957,958],{"class":72}," session, conn\n",[62,960,961,963],{"class":64,"line":343},[62,962,420],{"class":68},[62,964,662],{"class":72},[62,966,967,970,973],{"class":64,"line":349},[62,968,969],{"class":68}," except",[62,971,972],{"class":186}," Exception",[62,974,364],{"class":72},[62,976,977,979],{"class":64,"line":355},[62,978,420],{"class":68},[62,980,981],{"class":72}," conn.rollback()\n",[62,983,984],{"class":64,"line":367},[62,985,986],{"class":68}," raise\n",[62,988,989,992],{"class":64,"line":373},[62,990,991],{"class":68}," finally",[62,993,364],{"class":72},[62,995,996,998],{"class":64,"line":384},[62,997,420],{"class":68},[62,999,1000],{"class":72}," session.close()\n",[62,1002,1003],{"class":64,"line":395},[62,1004,125],{"emptyLinePlaceholder":124},[62,1006,1007],{"class":64,"line":401},[62,1008,1009],{"class":131},"# Usage:\n",[62,1011,1012],{"class":64,"line":406},[62,1013,1014],{"class":131},"# async with hybrid_transaction_scope(session_factory) as (session, conn):\n",[62,1016,1017],{"class":64,"line":412},[62,1018,1019],{"class":131},"# await session.add(domain_entity) # ORM operation\n",[62,1021,1022],{"class":64,"line":426},[62,1023,1024],{"class":131},"# await conn.execute(bulk_etl_stmt) # Core operation\n",[62,1026,1027],{"class":64,"line":447},[62,1028,1029],{"class":131},"# # Both share the same transactional boundary\n",[14,1031,1033],{"id":1032},"production-pitfalls-anti-patterns","Production Pitfalls & Anti-Patterns",[1035,1036,1037,1047,1060,1073,1082],"ul",{},[1038,1039,1040,1043,1044,51],"li",{},[759,1041,1042],{},"Overusing ORM for bulk migrations:"," Causes N+1 identity map bloat and excessive memory consumption. Route datasets >10k rows through Core ",[26,1045,1046],{},"insert().executemany()",[1038,1048,1049,1059],{},[759,1050,1051,1052,729,1055,1058],{},"Mixing ",[26,1053,1054],{},"Connection",[26,1056,1057],{},"Session"," without synchronization:"," Leads to transaction state divergence. Always extract the connection from an active session or use a shared transactional context manager.",[1038,1061,1062,1068,1069,1072],{},[759,1063,1064,1065,1067],{},"Neglecting ",[26,1066,712],{}," in async workers:"," Triggers detached instance errors on lazy loads after ",[26,1070,1071],{},"await session.commit()",". Explicitly configure session factories for background consumers.",[1038,1074,1075,1081],{},[759,1076,1077,1078,1080],{},"Applying ORM loaders to Core ",[26,1079,32],{},":"," Results in silent query failures. Loaders are ORM-specific and must be stripped from Core statement trees.",[1038,1083,1084,1087,1088,729,1090,1093,1094,1097],{},[759,1085,1086],{},"Ignoring pool health checks:"," Failing to configure ",[26,1089,728],{},[26,1091,1092],{},"pool_recycle"," for long-lived async pools under high concurrency causes intermittent ",[26,1095,1096],{},"ConnectionResetError"," and connection starvation.",[14,1099,1101],{"id":1100},"frequently-asked-questions","Frequently Asked Questions",[19,1103,1104,1107],{},[759,1105,1106],{},"When should I choose SQLAlchemy Core over ORM in 2.0?","\nChoose Core for high-volume ETL, dynamic schema generation, complex aggregations, or when strict memory\u002Fperformance constraints require bypassing the identity map and unit-of-work overhead.",[19,1109,1110,1113,1114,1116,1117,1120,1121,1123,1124,1126],{},[759,1111,1112],{},"Does SQLAlchemy 2.0 still require separate APIs for Core and ORM?","\nNo. 2.0 unifies both under the ",[26,1115,32],{}," construct and ",[26,1118,1119],{},"execute()"," method. The difference lies in the execution context (",[26,1122,1054],{}," vs ",[26,1125,1057],{},") and result hydration behavior.",[19,1128,1129,1132,1133,1136,1137,1139,1140,1142],{},[759,1130,1131],{},"Can I use async workflows with both Core and ORM simultaneously?","\nYes. ",[26,1134,1135],{},"AsyncEngine"," supports both ",[26,1138,520],{}," (Core) and ",[26,1141,524],{}," (ORM). They share the same underlying async driver pool but require careful transaction boundary and event loop management.",[19,1144,1145,1148,1149,779,1152,1155,1156,1158,1159,779,1162,1165],{},[759,1146,1147],{},"How do I handle bulk inserts efficiently in 2.0?","\nUse Core's ",[26,1150,1151],{},"insert().returning()",[26,1153,1154],{},"executemany()"," with ",[26,1157,520],{},". Avoid ORM ",[26,1160,1161],{},"add_all()",[26,1163,1164],{},"bulk_save_objects()"," for datasets exceeding 10k rows due to session flush overhead.",[1167,1168,1169],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":58,"searchDepth":82,"depth":82,"links":1171},[1172,1173,1174,1175,1176,1177,1178],{"id":16,"depth":82,"text":17},{"id":498,"depth":82,"text":499},{"id":701,"depth":82,"text":702},{"id":736,"depth":82,"text":737},{"id":786,"depth":82,"text":787},{"id":1032,"depth":82,"text":1033},{"id":1100,"depth":82,"text":1101},"md",{},"\u002Fmastering-sqlalchemy-20-core-and-orm-architecture\u002Fcore-vs-orm-architecture-decisions",{"title":5,"description":58},"mastering-sqlalchemy-20-core-and-orm-architecture\u002Fcore-vs-orm-architecture-decisions\u002Findex","-GYu_uZXwLc-GZw9eJqSo75BgQEK7tOU3KeB6s8tDX8",1778149144401]