{"id":2922,"date":"2024-08-24T12:18:43","date_gmt":"2024-08-24T11:18:43","guid":{"rendered":"https:\/\/contentlabstudy.com\/soft\/?p=2922"},"modified":"2026-09-24T21:15:40","modified_gmt":"2026-09-24T20:15:40","slug":"orm-features","status":"publish","type":"post","link":"https:\/\/contentlabstudy.com\/soft\/orm-features\/","title":{"rendered":"ORM Features"},"content":{"rendered":"<p>An <strong>ORM<\/strong> maps objects to tables so you can persist a graph without hand-writing every INSERT. EF Core, Hibernate, Django ORM all do this. They are not a reason to skip SQL, indexes, or transactions. When the query is the product, write SQL (or a compiled view) and map the result.<\/p>\n<h2>What it should give you<\/h2>\n<ul>\n<li><strong>Unit of work:<\/strong> one EF Core <code>SaveChanges<\/code> starts a transaction when none is open. A Hibernate flush uses the transaction you already started; it does not open one by itself.<\/li>\n<li><strong>Identity map:<\/strong> the same row is the same object in a context \u2014 until you dispose the context.<\/li>\n<li><strong>Change tracking:<\/strong> conveniences and surprises (N+1, extra UPDATEs).<\/li>\n<li><strong>Migrations:<\/strong> schema as code. Still review the generated SQL.<\/li>\n<\/ul>\n<h2>N+1 and lazy load<\/h2>\n<p>Looping <code>order.Lines<\/code> with lazy load fires a query per order. Fix with explicit <code>Include<\/code> \/ fetch-join, a projection (<code>Select<\/code> into a DTO), or a second query you control. Lazy load in a web request is a footgun; many teams disable it.<\/p>\n<h2>Tracking vs no-tracking<\/h2>\n<p>Read-only list endpoints: no-tracking (or a micro-ORM). Tracking is for graphs you intend to update. Mixing a long-lived tracked context with a web request is how you get stale entities and accidental updates.<\/p>\n<h2>Example<\/h2>\n<p>One query per order, then a projection that lets the database add the lines.<\/p>\n<pre><code>var orders = await db.Orders.ToListAsync(ct);\nforeach (var order in orders)\n{\n    var lines = await db.Lines.Where(line =&gt; line.OrderId == order.Id).ToListAsync(ct);\n    _ = lines.Count;\n}<\/code><\/pre>\n<pre><code>var rows = await db.Orders\n    .Select(order =&gt; new OrderTotal(order.Id, order.Lines.Sum(line =&gt; line.Amount)))\n    .ToListAsync(ct);<\/code><\/pre>\n<h2>What breaks it<\/h2>\n<p>Returning <code>IQueryable<\/code> from the repository means the controller decides the SQL. Return the DTO from the projection above instead.<\/p>\n<pre><code>static IQueryable&lt;Order&gt; All(AppDb db) =&gt; db.Orders;<\/code><\/pre>\n<h2>Pitfalls<\/h2>\n<ul>\n<li>Treating entities as API contracts (over-posting, leaking columns).<\/li>\n<li>Repository-on-top-of-repository that returns <code>IQueryable<\/code> to the controller \u2014 the ORM is now your API.<\/li>\n<li>Ignoring the SQL in logs. If you cannot explain the query, you do not own the cost.<\/li>\n<\/ul>\n<p>See <a href=\"\/soft\/sql-for-business-logic\/\">SQL for business logic<\/a> and <a href=\"\/soft\/database-normalization\/\">normalization<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>An ORM maps objects to tables so you can persist a graph without hand-writing every INSERT. EF Core, Hibernate, Django [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[3],"tags":[],"class_list":["post-2922","post","type-post","status-publish","format-standard","hentry","category-software-development"],"_links":{"self":[{"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/posts\/2922","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/comments?post=2922"}],"version-history":[{"count":4,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/posts\/2922\/revisions"}],"predecessor-version":[{"id":3229,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/posts\/2922\/revisions\/3229"}],"wp:attachment":[{"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/media?parent=2922"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/categories?post=2922"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/contentlabstudy.com\/soft\/wp-json\/wp\/v2\/tags?post=2922"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}