views:

1671

answers:

4

I started messing with EF 4.0 because I am curious about the POCO possibilities... I wanted to simulate disconnected web environment and wrote the following code to simulate this:

  1. Save a test object in the database.
  2. Retrieve the test object
  3. Dispose of the DataContext associated with the test object I used to retrieve it
  4. Update the test object
  5. Create a new data context and persist the changes on the test object that are automatically tracked within the DynamicProxy generated against my POCO object.

The problem is that when I call dataContext.SaveChanges in the Test method above, the updates are not applied. The testStore entity shows a status of "Modified" when I check its EntityStateTracker, but it is no longer modified when I view it within the new dataContext's Stores property. I would have thought that calling the Attach method on the new dataContext would also bring the object's "Modified" state over, but that appears to not be the case. Is there something I am missing? I am definitely working with self-tracking POCOs using DynamicProxies.

private static void SaveTestStore(string storeName = "TestStore")
{
  using (var context = new DataContext())
  {
    Store newStore = context.Stores.CreateObject();
    newStore.Name = storeName;
    context.Stores.AddObject(newStore);
    context.SaveChanges();
  }
}

private static Store GetStore(string storeName = "TestStore")
{
  using (var context = new DataContext())
  {
    return (from store in context.Stores
            where store.Name == storeName
            select store).SingleOrDefault();
  }
}

[Test]
public void Test_Store_Update_Using_Different_DataContext()
{
  SaveTestStore();
  Store testStore = GetStore();
  testStore.Name = "Updated";      

  using (var dataContext = new DataContext())
  {
    dataContext.Stores.Attach(testStore);
    dataContext.SaveChanges(SaveOptions.DetectChangesBeforeSave);        
  }

  Store updatedStore = GetStore("Updated");
  Assert.IsNotNull(updatedStore);
}
+2  A: 

Hello, Rob..

After playing around with the self-tracking entities, I realised what was your mistake. Instead of trying to attach the entity to the data context, you should instead instruct that you want the data context to apply the new changes you have made to it to the database.

In this case, change the "saving" code to this:

using (var dataContext = new DataContext())
{
    dataContext.Stores.ApplyChanges(testStore);
    dataContext.SaveChanges();        
}

At least I have tested it on my local machine, and it worked after this update :)
Hope this helps!

Artiom Chilaru
How did you get the ApplyChanges() method to show up? I must be doing something wrong because that method does not exist for me...
Rob Packwood
Oh I see. You are using the Self-Tracking Entity code generation template whereas I am working with the POCO template. With the POCO template the entities track themselves through some trickery by means of a DynamicProxy overriding the POCO object at runtime with some special change tracking abilities. I just can't get my scenario in my original question to work after trying what seems to be everything.
Rob Packwood
I have initially started my tests with POCO entities, but I couldn't find a way how to get the "State" of the object (Modified/Unchanged), while you were saying it was there. After rereading your post I saw "I am definitely working with self-tracking POCOs" so I tried the self tracking template... So where did you get the state of the object from??
Artiom Chilaru
+4  A: 

As you stated later, you were using the POCO generator, not the self-tracking entities generator.

I've tried it as well, and became quite perplexed. It seems that the proxy classes don't quite work as expected, and there might be a bug. Then again. none of the examples on MSDN try something like this, and when they reference updates in different tiers of an app (something like we're doing here) they use self-tracking entities, not POCO proxies.

I'm not sure how these proxies work, but they do seem to store some kind of state (I managed to find the "Modified" state inside the private properties). But it seems that this property is COMPLETELY ignored. When you attach a property to a context, the context adds an entry to the ObjectStateManager, and it stores further state updates in there. At this point if you make a change - it will be registered, and applied.

The problem is that when you .Attach an entity - the Modified state from the proxy is not transferred to the state manager inside the context. Furthermore, if you use context.Refresh() the updates are override, and forgotten! Even if you pass RefreshMode.ClientWins into it. I tried setting the object state's state property to modified, but it was overridden anyway, and the original settings were restored..

It seems that there's a bug in the EF right not, and the only way to do this would be to use something like this:

using (var db = new Entities())
{
    var newUser = (from u in db.Users
                    where u.Id == user.Id
                    select u).SingleOrDefault();
    db.Users.ApplyCurrentValues(user);
    db.SaveChanges();
}

One more thing here

http://stackoverflow.com/questions/2314995/entitity-framework-change-tracking-in-soa-with-poco-approach

It seems that POCO just doesn't support the approach you're looking for, and as I expected the self-tracking entities were created to tackle the situation you were testing, while POCO's proxies track changes only within the context they created.. Or so it seems...

Artiom Chilaru
Thanks for all the hard work! The fact that POCOs are only tracking in the context they were created in is really unfortunate. I almost hope it is a bug and not just how it was designed. I was trying to avoid reloading the entity from the database during a save... With the original and update entities both loaded of course it is easy to know if changes took place.
Rob Packwood
+1 for pointing out the distinction between POCO template generated entities and the STE POCO template generated entities
qntmfred
+3  A: 

Try

        db.ObjectStateManager.ChangeObjectState(user, System.Data.EntityState.Modified);

Before calling SaveChanges

Aidan
note that Mrs Entity Framework herself, Julie Lerman, has also suggested this solution http://thedatafarm.com/blog/data-access/my-inbox-how-to-save-changes-in-ef-coming-from-disconnected-pocos/
qntmfred
A: 

I think the root of your problem is your management of the Context object.

With POCO disposing the context does not notify the entities on that context that they are no longer associated with a context. The change tracking with POCO is all managed by the context so you get into some fun problems where the POCO will act like it is still attached to a context but in reality it is not and re-attaching to another context should throw an error about attaching to multiple contexts.

there is a small post about this you may want to read here: http://social.msdn.microsoft.com/forums/en-US/adodotnetentityframework/thread/5ee5db93-f8f3-44ef-8615-5002949bea71/

If you switch to self tracking I think you'll find your entities work the way you're wanting.

another option is to add a property to a partial class of your poco to track changes manually after detaching the POCO from the context you used to load it.

Jack