Attach() if you have something detached


It is clear from the forums that this whole business of attaching detached objects in LINQ to SQL (DLinq) is confusing. Some of it is intrinsic, some is perhaps our design and perhaps a bit attributable to the misnomer “detached object support”. More about the misnomer later but first here is the skinny on Attach() set of APIs. 


Think of a DataContext instance as a happy universe of objects where the entities (or objects with unique keys) are known to the DataContext instance since they were retrieved using that instance and the unknown ones are simply new entities that need to be inserted. Unfortunately, this simple picture is complicated by the fact that in a multi-tier system, entities may be sent over the wire in an ASP.NET app or web service client and then they may need to be brought back to the mid-tier for update. Add to that the fact that the mid-tier is often stateless so the DataContext instance used to retrieve the entity from the database may be long gone. A new DataContext instance can be spun up for update but the new one does not know about the deserialized entity that it did not retrieve. Attach() solves this problem by telling the new DataContext instance that the attached entity is meant to be updated and should not be inserted into the database.


Attach() needs to preserve the optimistic concurrency capability since that is how concurrent change conflicts can be detected and handled. So Attach() needs to deal with current and original values. This is what gives rise to the different flavors.



  1. Original values used for conflict detection: db.Customers.Attach(originalCust) should be used to attach the original values. The instance can then be modified (playback) before calling SubmitChanges().

  2. Original and current copies available: db.Customers.Attach(currentCust, originalCust) does it in one shot. Of course, this requires two instances with original and current values respectively.

  3. Timestamp or no optimistic concurrency members: db.Products.Attach(currentProd) In this case, original values are not required so current entity instance is enough. There is no playback needed before calling SubmitChanges().

When it comes to “detached object support”, a question I often get is – what do I call to detach an entity? The answer is nothing! If an entity is serialized and deserialized back, it is already detached from the DataContext instance used for retrieval. If not, it may have deferred loaders that are tethered to the original DataContext. More significantly, Attach() is not intended to enable movement of entities across DataContext instances in the same app domain. This is not the intent (more about why that’s the case some other day). In fact as I have mentioned on the LINQ forum, this is likely to cause an exception sooner or later so we have modified Attach() to throw if it is used on an object that is still attached to some DataContext instance. This change was done after beta2 and should be visible in RTM.


Detachedly yours,


Dinesh


Comments (20)

  1. Krish says:

    Hi Dinesh,

    I agree that the whole notion of attaching/detaching entity instances is confusing – especially so in a multi-tier scenario. Maybe this calls for a rethink of the method names? What if the "Attach" method overloads that take 2 parameters were renamed to a "Merge" method? Since this is closer to the semantics to what is actually taking place.

    The "Attach" overload that takes a single parameter still makes sense since you want associate an instance with a DataContext (for playback).

    Regards,

    -krish

  2. Roller says:

    It is clear from the forums that this whole business of attaching detached objects in LINQ to SQL (DLinq)

  3. Dinesh Kulkarni, program manager on the LINQ to SQL project, presents the logic behind LINQ to SQL’s

  4. Dinesh Kulkarni, program manager on the LINQ to SQL project, presents the logic behind LINQ to SQL’s

  5. Dinesh Kulkarni, program manager on the LINQ to SQL project, presents the logic behind LINQ to SQL’s

  6. Rick Strahl says:

    Dinesh,

    So how does Attach ‘know’ that an entity was attached to a different DataContext if the entity itself has no state (it’s derived from Object after all)?

    The issue that comes up is if you pass object across TIERS (not necessarily physical tiers) where there maybe multiple DataContexts on different business objects working with entities.

    If you can deal with deserialized copies, then why make a distinction between that and a ‘formerly attached’ object from a different context? Surely it can’t be difficult to dump original object context whatever that may be. It’s just very inconsistent of the API. You’re basically forcing us as developers to go through girations to make this happen (say manually serialize/deserialize).

    Maybe you could do another post and demonstrate how you envision how to do the .Attach() with the current and original values including describing some scenarios where the original values come from.

    One more thing: Currently you can get .Attach to work without a second original values instance if the entity/table has a timestamp. Is that behavior still there?

  7. Rick,

    Two key points about working with detached objects

    1. There are actually deferred loaders in EntityRef/EntitySets if deferred loading is not turned off. If it is, then the objects are effectively detached.

    2. It is possible for us to forcibly remove the deferred loaders. I am not convinced, we want to particularly support this pattern and until then at least, we would rather point out the lack of support than let you incorrectly assume that this is the intent. This is worth a post.

    Your comment about showing the common pattern with Attach() is on the mark. I will add it to my list (or maybe Matt’s 🙂 )

    Attach() w/o second original instance did and will continue to work for timestamp-based opt concurrency.

    Dinesh

  8. Krish,

    Merge() has a certain legacy semantics with DataSet. That is really collection level semantics. So that really wasn’t a candidate.

    Also, the goal is to bring an object not known to the DataContext into the context. In ORM parlance (e.g. hibernate, EJB and in our world ObjectSpaces), this has long been called detached object support so ORMers often ask about it. That is where the name Attach() comes from. We didn’t see much value in breaking from this precedent.

  9. JP says:

    In a scenario where an application wants to allow different data providers, is it possible to use the entities generated by the designer and let each provider attach them.

    In the case of the SqlServer provider of course the DataContext would be the one managing the entites and we are back to the case without providers.

    The scenario I am thinking of is:

    A generic business object: BO<TEntity> with among other things basic CRUD methods, that delegates to each provider to implement these methods.

    The providers return IEnumerables so that the UI does not query the DB directly.

  10. Welcome to the thirty-third edition of Community Convergence. This week we have a new video called Programming

  11. JP,

    We worked really hard to in fact avoid or at least minimize any coupling between and entity and database. LINQ to SQL was not designed fort the ActiveRecord style, "entity has CRUD methods" pattern. In fact, IMO, it is a great anti-pattern for LINQ to SQL apps.

    Getting off the soap box, you could imagine a wrapper for DataContext instead of an entity that does what you seem to be aiming for.

    HTH

    Dinesh

  12. Jay Iyer says:

    I am curious about the third flavor of Attach:

    db.Products.Attach(currentProd)

    What happens if there is an old version of the product instance sitting in the DataContext? Will playback happen, i.e will all the changes that are present in the currentProd be applied to the DataContext?

    I am thiking of a scenario where the DataContext state is saved in the session (I know this is frowned upon, but I am thinking of using someting like this is in a serious enterprise app where application richness is important and there are not that many users) and the object is serialized and then deserialzed, and I want to attach the current version of the instance back to the DataContext.

    Thanks,

    Jay

  13. Jay,

    Attach() will fail if an object with matching object identity (same unique key values) exists in the DataContext.

    In your example above, DataContext state could be saved but it will have to be restored to a new DataContext instance. At that point, flavor #2 would be appropriate to use instead of #3.

    Dinesh

  14. Jay Iyer says:

    Thanks Dinesh for your prompt response.

    Followup questions:

    1. I am not sure if I follow your recommendation to restore the old DataContext state to a new DataContext. Is that a datacontext copy of some sort? How does that cicumvent the matching identity issue. Wouldn’t the ‘restored’ datacontext have an object with the same identity as the object we are trying to attach?

    2. The larger problem I am trying to address, and you might be able to point me in the right direction, is how do you work with a datacontext across multiple request/response cycles without hitting the database. This would be useful if you are doing a series of actions with multiple postbacks (and heavy invocatoin of methods in the model during every postback), but want to treat the entire workflow as a unit and want to commit/rollback the entire unit of work.

    3. I guess, I could loop through all the properties of the old instance in the DataContext and modify it to the current values im my modified instance. Wouldn’t it be useful to have a flavor of Attach (or some other DataContext method) which would do this?

    Thanks,

    Jay

  15. LINQ to SQL: Table&lt;T&gt;.Detach Method Does Not Exist Moving from Visual Studio 2008 Beta 2 to Visual

  16. The common guidance for updates is to keep the original values in view state so that you can recreate

  17. The common guidance for updates is to keep the original values in view state so that you can recreate

  18. Back to the "tips" series after a little break … One common question I get is about caching of data