When working with the ObjectContext in LINQ To Entities, a lot of operations are easily performed as long as you work with the same ObjectContext instance: You can retrieve entities from storage by selecting them; update or delete these entities and create new entities, and the ObjectContext will keep track of all this for you, so the changes are correctly applied to the store when you call SaveChanges.

This is all well and good, but not particularly useful when you start working with layered applications. In this case, LINQ To Entities is just a persistence technology that you (or someone else) decided to use to implement the Data Access Layer. A few years ago, I tended to implement my Data Access Components in straight ADO.NET; and a lot of people prefer NHibernate or similar tools - but I digress…

When LINQ To Entities is just an implementation detail of a service, lifetime management becomes important, so it is commonly recommended that any ObjectContext instance is instantiated when needed and disposed immediately after use.

This means that you will have a lot of detached entities in your system. Entities are likely to be returned to the calling code as interface, and when updating, a client will simply pass a reference to some implementation of that interface.

public void CompleteAtSource(IRecord record)

Since we should always follow the Liskov Substitution Principle, we should not even try to cast the interface to an entity. Instead, we must populate a new instance of the entity in question with the correct data and save it.

That's not hard, but since we are creating a new instance of an entity that represents data that is already in the database, we must attach it to the ObjectContext so that it can start tracking it again.

Now we are getting to the heat of the matter, because this is done with the AttachTo method, which is woefully inadequately documented.

At first, I couldn't get it to work, and it wasn't very apparent to me what I did wrong, so although the answer is very simple, this post might save you a bit of time.

This was my first attempt:

using (MessageEntities store = 
    new MessageEntities(this.connectionString))
{
    Message m = new Message();
    m.Id = record.Id;
    m.InputReference = record.InputReference;
    m.State = 2;
    m.Text = record.Text;
 
    store.AttachTo("Messages", m);
 
    store.SaveChanges();
}

I find this approach very intuitive: Build the entity from the input parameter's data, attach it to the store and save the changes. Unfortunately, this approach is wrong.

What happens is that when you invoke AttachTo, the state of the entity becomes Unchanged, and thus, not updated.

The solution is so simple that I'm surprised it took me so long to arrive at it: Simply call AttachTo right after setting the Id property:

using (MessageEntities store = 
    new MessageEntities(this.connectionString))
{
    Message m = new Message();
    m.Id = record.Id;
 
    store.AttachTo("Messages", m);
 
    m.InputReference = record.InputReference;
    m.State = 2;
    m.Text = record.Text;
 
    store.SaveChanges();
}

You can't invoke AttachTo before adding the Id, since this method requires that the entity has a populated EntityKey before it can be attached, but as soon as you begin updating properties after the call to AttachTo, the entity's state changes to Modified, and SaveChanges now updates the data in the database.

That you have to follow this specific sequence when re-attaching data to the ObjectContext is poorly documented and not enforced by the API, so I thought I'd share this in case it would save someone else a bit of time.


Comments

Luckily, the ADO.NET team implemented the attach logic correctly in the Entity framework as opposed to Linq to Sql.
2009-03-10 12:39 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Sunday, 22 February 2009 20:45:36 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!