skip to content

Detachment & Reattachment

Objects that leave the tracked set and come back: reattaching the same instance versus copying incoming state onto a freshly loaded one. Asked because a stale copy overwrites someone else's write.

on this pageshow

questions

4

In a data-access layer with a tracked set, what happens to an object's unwritten edits and unresolved links when it is detached?

level: juniorimportance: must knowfreq 72%

answer

  1. membership change, not an object change
  2. nobody is watching it any more
  3. no baseline compare at flush
  4. unflushed edits ride on the flush
  5. stand-ins have nothing left behind them

basics

~20 s

Detaching 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.

solid answer

~50 s

Detachment is a membership change, not a change to the object. While an object belongs to the tracked set the layer holds it in its **identity map**, compares it against the baseline it took at load time to spot edits, and can resolve a deferred link through the set when it is first touched. Detaching stops all three at once: the instance you hold becomes an ordinary in-memory object with the same field values. Edits made after it leaves are never flushed, and edits made before it leaves are written only if a flush had already turned them into statements — most layers do not flush merely because you detach. An association that never resolved stays unresolved. Objects usually become detached when the unit of work closes, when they are evicted to keep the set small, or when they cross a serialisation boundary.

go deeper

for a junior

Recall that a detached object is a plain object the layer no longer watches. Changing its fields saves nothing until it is put back under a live unit of work.

for a middle

Explain the three services membership provides — one instance per row, baseline comparison for edits, and resolving deferred links — and say that detachment withdraws all three without altering a single field value.

for a senior

Show that you know when detachment happens by accident: a set closing at the end of a request, an eviction to control memory, an object crossing a serialisation boundary. Load what downstream code will need before the object leaves.

for a principal

Frame it as a boundary decision. How far mapped objects travel from the set that loaded them determines how much of the codebase has to reason about detached state at all, and that is a design choice, not a mapper detail.

A data-access layer that implements a **unit of work** keeps a set of the objects it has loaded, or been handed, while one piece of work is in progress. Membership in that set is not a flag stored on the object; it is a relationship the layer maintains on the side, and it buys three concrete services. ## What membership actually provides - **Identity.** The set maintains an **identity map**: at most one live object per row for the duration of the work, so two lookups of the same key hand back the same instance rather than two copies that can drift apart. - **Change detection.** The layer keeps a baseline for every member — most commonly a snapshot of the field values taken when the row was read — and compares the object against it. That is why an ordinary field assignment turns into an update at the next flush without anyone calling a save method. - **Deferred loading.** An association the layer chose not to read up front is represented by a stand-in that knows how to fetch itself later. Resolving it needs the set, and behind the set a usable connection. **Detaching** ends that relationship. Nothing is copied and nothing is cleared: the very same instance, carrying the very same field values, is still in memory and still perfectly readable. What stops is every service in the list above. ## Tracked and detached, side by side | Aspect | While in the tracked set | After detachment | |---|---|---| | Assigning a field | noticed, becomes an update at flush | an ordinary memory write, observed by nobody | | Looking the key up again | returns this same instance | returns a different, freshly loaded instance | | An association not yet read | resolves when first touched | has no set behind it to resolve through | | The data the object holds | current as of the last read or write | frozen at the moment it left | ## Edits that had not been written yet It helps to separate two moments that beginners collapse into one. A **flush** is when accumulated changes become statements against the database; a **commit** is when those statements become durable. Detaching is neither. So an object that was edited and then left the set before any flush ran carries an edit that was never turned into a statement — the value is still there in memory, and the database never heard about it. Layers are not uniform about this, and the honest summary is that you should not depend on either behaviour: - Some layers flush pending work before evicting an object, so the edit is written. - Others simply drop the object out of the set, so the edit is silently lost. - Ending the whole unit of work is the common case, and there what matters is whether the work committed: a commit normally flushes first, while abandoning the work writes nothing. The practical rule: if the write matters, flush it explicitly while the object is still a member, and only then let it go. ## Links that never resolved An association left as an unresolved stand-in is a promise to fetch on demand, and the promise is only good while the set is there to honour it. After detachment the stand-in is still sitting in the field, still unresolved, and there is nothing behind it. Anything already resolved before the object left is unaffected — those values were copied into memory and stay readable. This is why the useful discipline is to decide, before the object leaves, exactly which associations the downstream code will touch, and make the read plan fetch them then. A detached object should be thought of as a self-contained snapshot: whatever was loaded into it is what it has, forever. ## How objects become detached 1. **The unit of work ends.** Closing or completing it detaches everything it held, all at once. This is by far the most common route, and it usually happens at a boundary somebody else wrote. 2. **Deliberate eviction.** Long-running work that loads many rows evicts objects, or clears the set wholesale, to stop it growing without limit. 3. **Crossing a boundary.** Anything serialised and sent elsewhere arrives as a plain object on the far side; whatever comes back was never in any set. 4. **Being built by hand.** An object constructed in code and populated from incoming data is untracked from birth, and to the layer it looks much like one that was detached. ## Working with detached objects on purpose Detachment is not a failure mode — it is how mapped data leaves a short unit of work and travels to code that has no business holding a connection. It becomes a problem only when code forgets which side of the line it is on. Two habits cover most of it: resolve everything you will need before the object leaves, and treat the object afterwards as a dated copy rather than a live view of the row. Getting its values back into the database is a separate act with its own choices, and it must be done inside a new unit of work.

  • Does detaching an object write its pending changes first?
    Not reliably. Writing happens at a flush, and detaching is not a flush in most layers; some flush before evicting, others simply drop the object and the edit with it. If the write matters, flush explicitly while the object is still tracked, then let it go.
  • Are the values already loaded still readable once the object is detached?
    Yes. Detachment clears nothing, so scalar fields and any association that was already resolved keep exactly the values they held. Only the work that needed the set stops: noticing edits, reusing one instance per row, and resolving a link that had never loaded.
  • What detaches an object besides an explicit call?
    Ending the unit of work detaches everything it held at once, which is the common case. So does evicting an object, or clearing the set, to stop long-running work growing without limit — and anything serialised and sent elsewhere arrives untracked on the far side.

saying these in an interview costs you the question

  • Thinks a detached object still saves itself when its fields change
  • Assumes detaching flushes the pending edits first
  • Believes detaching hands back a copy and keeps the original tracked
  • Expects an unresolved link to still fetch once the set is gone
  • Confuses detached with deleted, or with never-persisted
open as a page

When a detached object rejoins a tracked set, how does reattaching that instance differ from copying its state onto a freshly loaded one?

level: middleimportance: must knowfreq 66%

basics

~10 s

Reattaching makes the very instance you hold tracked again. A copy-in instead resolves the row's own instance, assigns your values onto it and returns that one, leaving the object you passed in detached forever.

open as a page

When a partially populated object is copied into a tracked set, how are its nulls, absent collection elements and linked objects treated?

level: middleimportance: should knowfreq 57%

basics

~20 s

A copy-in overwrites the target field by field, so a null in the incoming object stores a null, a collection is made to match the one handed in, and links are followed only where the mapping cascades the copy.

open as a page

An object edited outside the tracked set is written back an hour later and another user's change vanishes — why, and how do you fix the write path?

level: seniorimportance: should knowfreq 58%

basics

~20 s

The detached object is a snapshot of the row an hour earlier, and writing it back assigns every mapped field, so a column another writer changed is overwritten with the old value and the update still succeeds.

open as a page