Data Access & ORM Concepts
The ideas under every data-access layer: rows becoming objects that keep identity, links that load late, edits flushed at a boundary, schema change, cached copies. Asked because data bugs start here.
on this pageshowhide
explore
- Mapping & Identity31 questions
- Metadata, Naming & Class Demands5 questions
- Key Generation & Timing5 questions
- Associations & Ownership5 questions
- Inheritance & Polymorphic Queries5 questions
- Value Types & Converters6 questions
- Global Filters & Audit Stamps5 questions
- Loading Strategies29 questions
- Deferred Links & Proxies5 questions
- Deferred Reads After Close5 questions
- Eager Plans & Over-Fetching5 questions
- Batch & Subquery Fetching5 questions
- Cartesian Products & Duplicates5 questions
- Fetching Without Blocking4 questions
- N+1 Diagnosis & Repair23 questions
- Query-per-Row Origins4 questions
- Statement Counting & Budgets5 questions
- Remedy Selection by Cardinality4 questions
- Per-Row Writes & Batching5 questions
- Test-Time Blind Spots5 questions
- Unit of Work Mechanics29 questions
- Lifecycle States & Save Semantics5 questions
- Change Detection & Snapshots5 questions
- Flush Timing & Ordering5 questions
- Context & Connection Lifetime5 questions
- Identity Map & Instance Reuse5 questions
- Detachment & Reattachment4 questions
- Transactions from the Application34 questions
- Demarcation & Propagation6 questions
- Read-Only Units & Routing6 questions
- Optimistic Versioning6 questions
- Pessimistic Lock Requests5 questions
- Errors, Rollback & Retry6 questions
- Commit Side Effects5 questions
- Migrations & Drift18 questions
- Schema Ownership & Generation4 questions
- Deploy-Time Placement & Locks5 questions
- Backfills & Seed Rows5 questions
- Testing the Upgrade Path4 questions
- Querying & Raw SQL36 questions
- Repository Boundary4 questions
- Authoring Styles & Typed DSLs5 questions
- Dynamic Predicate Composition5 questions
- Projections & Transfer Models5 questions
- Native Statements & Row Mapping6 questions
- Bulk Writes & Streaming Reads6 questions
- Layers Without Tracking5 questions
- Caching Layers19 questions
- Cache Tiers Compared4 questions
- Shared Entity Cache5 questions
- Invalidation & Staleness5 questions
- Application Cache Boundary5 questions
questions
219 · 8 sectionsIn a data-access layer that maps a link from both classes, what does it mean that one end owns the write?
basics
~20 sThe owning end is the side whose state the mapper turns into the foreign-key or junction-row statement. The other end is mapped for reading and navigation: changing it alone normally produces no SQL at all.
In a data-access layer, what is an always-on filter on a mapped type, and which reads does it affect?
basics
~20 sAn always-on filter is a predicate declared once on a mapped type, such as live-rows-only or current-tenant-only, that the layer adds to the WHERE clause of the statements it generates for that type, so no caller has to restate it.
When the store assigns a key during the insert, at what point does a mapped object learn its identifier?
basics
~20 sOnly when the insert actually runs. Until that statement reaches the store the key field is empty, so asking a pending object for its identifier forces the layer to write that row immediately and read the generated value back.
When a data-access layer maps a class hierarchy, how does it decide which concrete subclass to instantiate for a row?
basics
~20 sEither a type-marker column stored with the row names the class, or the layer discovers which subtype table holds a row for that key. The marker is free with the read; discovery costs a join or a probe per subtype.
Where can a data-access layer's mapping rules live, and what does convention-only mapping mean?
basics
~20 sMapping rules have four possible homes: on the class itself, in an external configuration document, in a startup configuration API, or nowhere — derived by convention from class and property names. Most layers mix conventions with targeted overrides.
What does it mean for a data-access layer to fill deferred links in identifier batches rather than one at a time?
basics
~20 sRather than one statement per touched stand-in, the layer gathers the keys of other unfilled stand-ins of the same kind and fetches them together with a set-membership predicate, turning N secondary statements into roughly N divided by the batch size.
When one statement fetches parents together with their child collection, why does the same parent come back repeatedly?
basics
~20 sThe join returns one row per matching child, with the parent's columns repeated on every one of them. A layer that produces one result entry per row therefore hands back the same parent once per child row.
In an object-relational mapper, what does it mean for a link to be eagerly fetched, and when does that data arrive?
basics
~20 sEager means the linked data is loaded as part of the read that returns its owner - in the same statement through a join, or in an immediate follow-up statement - so nothing has to load later when code touches the link.
When a data-access layer returns a deferred stand-in for a related object, which accesses fire a load and which do not?
basics
~20 sAny access that needs data the row has not supplied fires the load: reading a mapped field, iterating or sizing a collection, or calling a behaviour method on the stand-in. Reading the identifier it already holds does not.
What happens when code touches a not-yet-loaded association after the unit of work that loaded the object has closed?
basics
~20 sThe read fails at that moment. A deferred link still needs its own query to fill itself, and the unit of work and connection that would run it are gone, so most layers raise an error instead of returning data.
When a data-access layer serves one request, what does counting its SQL statements reveal that the request's duration does not?
basics
~20 sA statement count exposes the shape of the access pattern - one statement per row versus a fixed few - independently of machine speed or data volume. Duration says only that something was slow, once it already is.
In a data-access layer, what does batching accumulated writes mean, and why is it faster than one statement per row?
basics
~10 sBatching sends many same-shaped statements in one round trip, one parameter set per row, instead of a request-and-wait per row. The database still does each row's work; what disappears is the per-row waiting.
When a list read fires one query per row, what remedies remove the repeated queries?
basics
~20 sFour families: pull the link into the parents' statement with a join fetch, load the whole page's links in one extra bulk statement, project the read down to the fields it needs, or serve the link from a cache.
In a layer that loads linked data on first touch, which line of code emits the extra per-row statements?
basics
~10 sThe line that first touches a deferred link while iterating — an ordinary field read, not a query call. The list read is one statement; every touch inside the iteration adds another.
In a data-access test, why can reading a just-saved object back inside the same unit of work pass without the database storing it?
basics
~20 sA mapper holds saved objects in an in-memory identity map and defers the write until a flush. A read by the same id hands back that instance, so the assertion passes whether or not a statement ever reached the database.
When a data-access layer loads a row into a tracked object, what snapshot does it keep, and what is that snapshot for?
basics
~20 sA tracking layer copies each loaded row's column values into a private snapshot beside the object. At flush it compares the live object against that snapshot; fields that differ become the UPDATE, and an object with no difference produces none.
In a data-access layer with a tracked set, what happens to an object's unwritten edits and unresolved links when it is detached?
basics
~20 sDetaching removes the object from change tracking: later edits are no longer noticed or written, edits not yet flushed are usually lost, and links left as unresolved stand-ins have no set behind them to load through.
In a data-access layer that tracks loaded objects, what does a flush do, and how is it different from a commit?
basics
~20 sA flush turns the unit of work's accumulated in-memory changes into SQL statements sent on the already-open transaction. A commit ends that transaction, making the changes durable and visible to others. Flushed-but-uncommitted work is still discarded by a rollback.
In a data-access layer with an identity map, what does loading the same row twice inside one tracked set give you?
basics
~20 sYou get the same object both times. A tracked set's identity map, keyed by type plus identifier, makes one row exactly one object for the life of that set; a by-key lookup it already holds needs no statement.
In a data-access layer that tracks loaded objects, what do the transient, managed, detached and removed states mean?
basics
~20 sTransient means never stored and not watched. Managed means the tracked set holds it and will write its edits. Detached means it once was managed but its set has ended. Removed means a delete is planned.
In a data-access layer, what does an after-commit callback guarantee that code placed right after the last save does not?
basics
~20 sAn after-commit callback runs only once the transaction has actually committed, so its side effect can never announce a change that later disappears. Code written in line after the last save still sits inside the open, undecided transaction.
Why is a transaction boundary usually placed around one use case rather than around each repository call?
basics
~20 sA transaction commits or rolls back as a whole, so the boundary must enclose everything that has to be all-or-nothing - usually one use case. Per-call commits make each write permanent on its own, so a later failure leaves half-written data.
Why does a data-access layer translate database errors into its own exception types instead of exposing vendor codes?
basics
~20 sA translation step maps engine-specific codes and messages onto a small portable set of failure kinds - constraint violation, deadlock victim, serialization failure, lock or statement timeout, lost connection - so calling code branches on the kind, not on a number.
When a data-access layer loads an object with a pessimistic lock requested, what changes about the read, and how long does the lock last?
basics
~20 sThe layer turns the read into a locking read, so the row is claimed for your transaction as it is fetched. Other writers of that row wait. The lock is released only when the transaction boundary ends.
What does marking a unit of work read-only change about how a data-access layer handles the objects it loads?
basics
~20 sA read-only unit stops keeping a value snapshot of each loaded object, so no dirty check and no flush run at the end. Reads get a lighter, cheaper path, and anything modified inside the unit is simply not written.
When a data-access layer builds the schema from its mapping, what do its re-create, auto-alter, validate-only and none modes do?
basics
~20 sSchema-from-mapping modes differ in how far the layer will change the database: re-create drops and rebuilds the mapped tables, auto-alter adds what is missing in place, validate-only compares and refuses to start on a mismatch, and none touches nothing.
Why do teams replay the whole ordered chain of schema change units from an empty database in tests?
basics
~20 sReplaying every change unit from empty proves the chain itself still builds a valid schema, catching ordering and dependency errors that a developer's long-lived database hides. Running only the newest unit leaves the rest of the chain untested.
How do you write a backfill or seed insert so that re-running it after an interruption is harmless and resumes?
basics
~20 sWrite the predicate so it selects only outstanding work, compute values absolutely from source columns rather than adding to the current one, guard inserts on a unique key, and commit progress so a restart continues where it stopped.
When data must be moved alongside a schema change, what does writing it as SQL statements buy over running it through the mapping?
basics
~20 sSQL statements run set-based against the schema as that point in history left it; no later code edit can alter them. Running the change through the mapping adds logic the database lacks, but ties a historical step to moving code.
Why must a migration script generated by diffing the mapping against the last known schema be reviewed before it runs?
basics
~20 sA diff compares two end states, not the edit that produced them, so it cannot see intent. A renamed field reaches the script as a dropped column plus an added one, which destroys the data unless a human rewrites it.
In a data-access layer, what is a set-based update, and how does it differ from load-modify-flush?
basics
~20 sA set-based update sends one UPDATE ... WHERE statement to the database through the layer. Load-modify-flush instead reads matching rows into objects, changes them in memory, and lets the layer write each changed object back.
When a search filter arrives empty, why should a composed query omit its predicate instead of adding an always-true condition?
basics
~20 sAn absent filter should contribute no predicate at all. A constant-true pad is only a concatenation crutch, a self-comparison silently drops rows holding no value, and a parameter-driven catch-all emits a different statement whose one plan must serve every combination.
In a data-access layer, what is a native statement, and what result shapes can it be mapped into?
basics
~20 sA native statement is SQL you write in the database's own language and hand to the layer unchanged, rather than an object-level query it translates. Its rows map back to entity objects, transfer models, or scalars.
In a data-access layer, what is an object-level query string, and when are its mistakes caught?
basics
~20 sAn object-level query is text naming mapped classes and fields, not tables and columns; the layer parses it and emits SQL. Being text, a typo or a renamed field surfaces only at parse time - startup or first run.
In a data-access layer, what does it mean to project a query into a transfer type, tuple or scalar?
basics
~20 sA projection asks the statement for only the fields a use case needs and materialises them as a scalar, a tuple or a small transfer type, so the row is never turned into a tracked mapped object.
In a data-access layer that caches loaded rows, what makes a cached entry stale, and which writes leave one behind?
basics
~20 sA cached entry goes stale when the row it copies changes and nothing removes the copy. Any write the caching layer never sees can leave one behind: a set-based statement, a native statement, another service, or an operator at a console.
Where should a service read and populate an application cache, relative to its transaction?
basics
~20 sRead before the unit of work opens, so a hit costs no connection or transaction. Populate only after the commit returns, never from inside the transaction or the mapper's flush path, so nothing that could still roll back is published.
What does an application cache of transfer models give up that the mapper's own cache keeps?
basics
~20 sIdentity, deferred completion and change detection. A cached answer is a detached snapshot with nothing behind it, so no shared instance, no link that can still load, no dirty checking. In exchange it skips the whole assembly path and travels between processes.
In a layer whose cache outlives one unit of work, why invalidate a row's entry both before the write and after the commit?
basics
~20 sRemoving before the write stops further hits on a copy about to become wrong; removing again after the commit drops the pre-image that a concurrent reader reloaded and put back during the write. Populate only after a real commit.