skip to content

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%

answer

  1. same instance, or the set's own instance
  2. one route hands you a different object
  3. keep the reference the call returns
  4. one row, one tracked instance

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.

solid answer

~50 s

Both routes end with the row's new state under the layer's control, and they differ in which object ends up tracked. Reattaching takes the detached instance itself and puts it back into the set, so the reference already in your hand is the managed one and later edits to it are noticed again. A copy-in does not adopt your instance: it finds or loads the tracked instance for that key, assigns the incoming field values onto it, and hands that instance back. The object you passed in stays detached, and anything you set on it afterwards goes nowhere — so the returned reference is the one to keep. Copy-in also tolerates a set that already holds the row, because it writes into that instance; reattaching a second object for a key the **identity map** already has is a conflict, since one row may have only one tracked instance.

go deeper

for a junior

Remember the practical rule: after a copy-in, work with the object the call returns, not the one you passed in. After a reattach, the object you already hold is the live one.

for a middle

Explain the mechanism. Reattach changes an instance's membership; copy-in transfers field values onto the instance the set holds for that key, which the identity map guarantees is the only one for that row.

for a senior

Show you have debugged the symptom: a save that appears to do nothing because the code kept mutating the detached input after a copy-in had returned a different reference. Nothing throws, and only some of the intended values land.

for a principal

Decide as policy whether mapped objects leave the set at all. A codebase that returns transfer models and applies incoming changes to freshly loaded objects never has to teach this distinction to anyone.

A detached object is an ordinary in-memory object that happens to carry the key of a row and a set of field values read at some earlier moment. To get those values into the database it has to come under the control of a live **unit of work** again, and data-access layers offer two shapes for that. They look interchangeable in a code review and are not. ## Route one: reattach the instance you hold Reattaching takes the object you are holding and makes it a member of the current tracked set. Afterwards the reference in your hand is the managed one: later assignments to its fields are noticed by change detection and written at the next flush, exactly as if you had loaded it here. - The instance's address does not change; nothing is copied. - The layer has to establish a baseline for change detection. Some layers assume the whole object is dirty and write every mapped field; others read the row's stored state and write only the differences. Either way, all the values the instance carries are candidates to be written. - Because an **identity map** allows at most one tracked object per row, reattaching is only possible when the set does not already hold an instance for that key. A second claimant cannot also be adopted. ## Route two: copy the state in A copy-in does not adopt your object at all. The layer resolves the tracked instance for that key — finding it in the identity map, or reading the row if it is not loaded — assigns the incoming field values onto it, and hands that instance back to you. Your object stays detached for the rest of its life. - It works when the set already holds the row, because it writes into the instance already held instead of competing with it. - It usually costs a read that reattaching does not, unless the target was already loaded. - The returned reference is the only tracked one. This is the whole trap. ## The two routes compared | | Reattach | Copy-in | |---|---|---| | Which object ends up tracked | the one you passed in | the set's own instance for that key | | What the caller should hold afterwards | the same reference as before | the reference the call returned | | Set already holds that row | conflict — one row, one instance | fine, values land on the held instance | | Extra read | often none | usually one, unless already loaded | | Your original object | is the live one | stays detached forever | ## The bug this distinction causes The classic symptom is a save that appears to do nothing. The code copies an object in, then continues editing the object it passed in — setting a status, stamping a timestamp — and commits. Those later edits land on a detached object that nothing is watching, so they are never written, while the values captured at the moment of the copy-in are. Nothing throws. The row is updated, just not with everything the code believed it had set. The fix is mechanical and worth making a house rule: 1. Assign the result of a copy-in to a variable and keep working with that. 2. Do not keep the input object in scope afterwards if you can avoid it; shadowing or reassigning removes the temptation. 3. In review, treat a copy-in whose return value is discarded as a defect on sight. ## Choosing between them Reattaching is attractive when the object genuinely round-tripped from this system and you want the same reference to stay live, and when you know nothing else in the current work has loaded that row. Copy-in is the more forgiving of the two precisely because it does not care what the set already holds, which is why it tends to be the default in code that receives objects from elsewhere. The deeper answer in an interview is that both are compromises. Whole mapped objects travelling outside the set and coming back is what creates the question at all. A write path that accepts a description of the change, loads the row inside the unit of work, and applies the change to the loaded object never has to explain the difference between the two routes to anyone — and never has to explain, either, why the object somebody kept editing did not save.

  • Why can reattaching fail when the set already holds an instance for the same row?
    The identity map allows exactly one tracked object per row, so two instances claiming the same key cannot both be managed. A copy-in sidesteps this by assigning the values into the instance already held instead of adopting a competing one.
  • Does a copy-in issue a read before it writes?
    Usually, unless the target instance is already in the identity map. The layer needs the row's own object to assign onto, so a copy-in typically costs an extra read that reattaching does not — worth knowing when a loop copies thousands of objects in.
  • After a reattach, are the edits made while the object was detached written?
    The values it carries are candidates to be written, but layers differ in how. Some treat the whole reattached object as dirty and write every mapped field; others take the row's stored state as the baseline and write only the differences. Either way, fields nobody meant to change can be written.

Reattaching is the clerk taking your form and filing it as the record. A copy-in is the clerk opening the office's own record, typing your values into it, and filing that one — your form goes home with you, and nothing you write on it afterwards counts.

saying these in an interview costs you the question

  • Keeps editing the object it passed in after a copy-in returns
  • Thinks both routes track the instance you handed over
  • Assumes reattaching works even when the set already holds that row
  • Believes a copy-in needs no read of the target row
  • Expects edits on the detached input to be flushed anyway