none
Entity Framework 4, POCO without self-tracking proxies, Detect Changes, WCF with ASP.NET Web Forms client

    Question

  • I'm trying to understand how to do detect changes in a POCO object. See this: http://msdn.microsoft.com/en-us/library/dd456854.aspx and this: http://msdn.microsoft.com/en-us/library/bb896271.aspx. I want to do it without self-tracking proxies .

    I have a Person object with various associations: Names collection, Addresses collection and Phone Numbers collection, etc.

    I have a WCF service which has an UpdatePerson method which accepts a Person object as a parameter. The Person object parameter is a "snapshot" of what I want the data in the database to be (the data in the various associations can be new, updated or deleted - e.g. new Name, deleted Address, updated Phone Number). How can Entity Framework know what is changed? When the client calls the function, the ObjectStateManager knows nothing about the original values.

    I was thinking I will need to get a Person object from the database including all its assocations: Names, Addresses, etc. This would represent the original data. Then Attach the updated Person object I got from the WCF method call and call Detect Changes? But I don't know how to do that?

    I tried doing this:

            public void UpdatePerson(Person person)
            {   
    	 // The ObjectStateManager is empty at this point
    	 // This will represent the "original" values (retrieves the data from the database)
                string[] associations = {"Names""Passports""Licences""IdentityDocuments""Addresses"};
                Person originalPerson = GetPersonById(person.PersonId.Value, associations);
    	 // The ObjectStateManager now knows about oldPerson
     
                // attach the Person object that represents what I want the Person record in the database to look like:
                newPerson.UpdatedWhen = DateTime.Now;
                db.People.Attach(person);
                db.ObjectStateManager.ChangeObjectState(person, System.Data.EntityState.Modified);

                // save all changes
                db.SaveChanges();

    But I got the following error:

    An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

     So basically, you can see I've got two Person objects:

    1. originalPerson represents the data that's currently in the database

    2. person represents the data that I WANT in the database. The Person's properties could be different from those in the database. There could be an extra Name or Address. The data of one of the Addresses could have been updated. One of the Names could have been deleted.

    Is the Entity Framework smart enough to compare originalPerson with person and update the database accordingly? I really hope so!

    Would it be something to do with the ApplyCurrentValues method? But that only does scalar values, right? How can that work with the associations (Names, Addresses, etc), especially if there's a Name added or an Address deleted?

    Note that I had a look at this How to article: http://msdn.microsoft.com/en-us/library/bb896248.aspx. In the introductory paragraph it describes my situation perfectly:

    This procedure is used when an object is updated remotely and set back to the server to persist the changes. If the object were simply attached to an object context at the server, updates would be lost or the operation would fail if the object was already in the object context.

    However, it's too simplistic. The example in the article updates a "SalesOrderDetail" object, which doesn't have any associations. I can't just iterate over each Name, Address, PhoneNumber, etc and call ApplyCurrentValues, because it looks like that only works for updates, not New or Deleted Names and Addresses.

    Tuesday, July 06, 2010 5:08 AM

Answers

  • Sorry, those links didn't really help. I am capable of doing a Google search.

    I worked it out myself. It involves a bit more coding - essentially I iterate over each association collection (I need to get the original collection from the database first) and use the keys to determine if it's insert, update or delete. I then call ObjectSet.DeleteObject, ObjectSet.ApplyCurrentValues or I add it to the original collection. At the end, I call SaveChanges. I was able to do this using a generic method with constraints on interfaces because each entity in the association implemented the same interface (e.g. they all had an Id property and a reference to their Person object).

    Thursday, July 15, 2010 10:09 PM

All replies

  • Hi,

    Why do not want to use self tracking entities? I you pass your entity through WCF, the change tracking is turned on by default and you don't have to track the changes by yourself.

    If you make some changes and want to persist them, you simply pass the entity back to your server through WCF and call something like this:

     

    using (LibraryEntities context = new Context() )

    {

             context.Books.ApplyChanges(entity);

             context.SaveChanges();

    }

     

    If you pass the entity back to your client, the state will be changed to UnChanged.

    If you want, I can supply some more examples.

     

    Greetings

    Tuesday, July 06, 2010 7:45 AM
  • Some examples would be good.

    I didn't want to use self-tracking POCO entities because I just want to pass the very simple POCO objects between the client and the server over WCF. I don't want to have the extra baggage that comes with the self-tracking proxies going over the wire. My client is a web application that is stateless. I create a new "Person" object using the posted-back data from the web controls at the time the user clicks "Save". This means I don't actually have a copy of the original object (with self-tracking or not) stored anywhere on the client, so even if the original object was self-tracking, it wouldn't be persisted anywhere. I'd like to avoid storing it in places like Session or ViewState. If I was using a Windows Forms or WPF application, there wouldn't be any problem. I would just follow this walkthrough: http://msdn.microsoft.com/en-us/library/ee789839.aspx.

    Besides, it looks like I wouldn't be able to store the self-tracking entity it in Session or ViewState anyway. According to the following article, "serialization to ASP.NET state management objects is not supported" (at the bottom of the article): http://msdn.microsoft.com/en-us/library/ff407090.aspx

    There must be a way of doing this without the self-tracking proxies.  I figure, if I can load the original entity out from the database, why can't Entity Framework use that for comparison with the updated entity? I think the How To article (link below) is sort of on the right track. It sort of does what I want. I just have a slightly more complicated example because I have my Names, Addresses etc associations where each item in an assocation could be New, Updated and/or Deleted. 

    http://msdn.microsoft.com/en-us/library/bb896248.aspx

    I realise I might have to do a bit more coding than just calling: context.People.ApplyChanges(person);

     

    Tuesday, July 06, 2010 10:11 PM
  • Hello,

     

    Since you are asking for some samples and references.  

     

    For the n-tier support in EF4, I concluded some great samples and references here:

    ============================================================================

    MSDN Magazine articles:

    The Entity Framework In Layered Architectures
    http://msdn.microsoft.com/en-us/magazine/cc700340.aspx 

    Anti-Patterns To Avoid In N-Tier Applications
    http://msdn.microsoft.com/en-us/magazine/dd882522.aspx 

    Entity Framework: N-Tier Application Patterns
    http://msdn.microsoft.com/en-us/magazine/ee321569.aspx 

    Building N-Tier Apps with EF4
    http://msdn.microsoft.com/en-us/magazine/ee335715.aspx 

    Entity Framework 4.0 and WCF Data Services 4.0 in Visual Studio 2010
    http://msdn.microsoft.com/en-us/magazine/ee336128.aspx 

    Entity Framework Design blog:

    Self-Tracking Entities in the Entity Framework
    http://blogs.msdn.com/b/efdesign/archive/2009/03/24/self-tracking-entities-in-the-entity-framework.aspx 

    N-Tier Improvements for Entity Framework
    http://blogs.msdn.com/b/efdesign/archive/2008/11/20/n-tier-improvements-for-entity-framework.aspx 

    Sample codes:

    Building N-Tier Apps with EF4
    http://code.msdn.microsoft.com/mag200911EF4 

    Entity Framework 4.0 and WCF Data Services 4.0 in Visual Studio 2010
    http://code.msdn.microsoft.com/mag201004VSData 

    Other helpful blogs:

    Updating data using Entity Framework in N-Tier and N-Layer Applications (short lived EF contexts) – (Part 1) (Cesar de la Torre)
    http://blogs.msdn.com/b/cesardelatorre/archive/2008/09/04/updating-data-using-entity-framework-in-n-tier-and-n-layer-applications-short-lived-ef-contexts.aspx 

    Optimistic Concurrency Updates using Entity Framework in N-Tier and N-Layer Applications (Part 2) (Cesar de la Torre)
    http://blogs.msdn.com/b/cesardelatorre/archive/2008/09/05/optimistic-concurrency-updates-using-entity-framework-in-n-tier-and-n-layer-applications-part-2.aspx

    N-Tier Methods in Entity Framework 4 – Use with Care (Julie Lerman)
    http://thedatafarm.com/blog/data-access/n-tier-methods-in-entity-framework-4-ndash-use-with-care/ 

    ============================================================================

     

     

    Good day!

     

     

    Best Regards,
    Lingzhi Sun

    MSDN Subscriber Support in Forum

    If you have any feedback on our support, please contact msdnmg@microsoft.com


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    Thursday, July 08, 2010 8:24 AM
  • Hi,

     

    I am writing to check the status of the issue on your side.  Would you mind letting us know the result of the suggestions? 

     

    If you need further assistance, please feel free to let me know.   I will be more than happy to be of assistance.

     

    Have a nice day!

     

     

    Best Regards,
    Lingzhi Sun

    MSDN Subscriber Support in Forum

    If you have any feedback on our support, please contact msdnmg@microsoft.com


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    Monday, July 12, 2010 8:07 AM
  • Sorry, those links didn't really help. I am capable of doing a Google search.

    I worked it out myself. It involves a bit more coding - essentially I iterate over each association collection (I need to get the original collection from the database first) and use the keys to determine if it's insert, update or delete. I then call ObjectSet.DeleteObject, ObjectSet.ApplyCurrentValues or I add it to the original collection. At the end, I call SaveChanges. I was able to do this using a generic method with constraints on interfaces because each entity in the association implemented the same interface (e.g. they all had an Id property and a reference to their Person object).

    Thursday, July 15, 2010 10:09 PM
  • I'm facing exactly the same problem as you describe. Can you help me out by posting an example of your generic method solution?

    Friday, July 16, 2010 9:22 AM
  •   private void UpdatePersonAssociation<T>(ICollection<T> original, ICollection<T> current, ObjectSet<T> contextObjectSet)
         where T : class, IPersonAssocation
      {
       // delete
       if (original != null)
       {
    
        List<T> toDelete = new List<T>();
        foreach (T originalEntity in original)
        {
         // does the original entity exist in the current entities?
         T exist = null;
    
         if (current != null)
         {
    
          exist = current.Where(currentEntity => currentEntity.Id.HasValue && currentEntity.Id.Value == originalEntity.Id.Value)
               .SingleOrDefault();
         }
         if (exist == null)
         {
          toDelete.Add(originalEntity);
         }
        }
    
        foreach (T originalEntityToDelete in toDelete)
        {
         // note that deleting is not currently audited. To enable this, we'd need to have a Deleted flag on the associated table
         // and update that. However, we'd also need to modify each "Get" operation to filter out deleted entities.
         contextObjectSet.DeleteObject(originalEntityToDelete);
        }
    
       }
    
       // add, update
       if (current != null)
       {
        foreach (T currentEntity in current)
        {
         // No need to set the UpdatedWhen. The trigger on the table will handle that.
         if (currentEntity.Id.HasValue)
         {
          contextObjectSet.ApplyCurrentValues(currentEntity);
         }
         else
         {
          // the current entity will now be associated with the original person, so
          // clear its reference to the current person
          if (original == null)
          {
           original = new List<T>();
          }
    
          currentEntity.Person = null;
          original.Add(currentEntity);
         }
        }
       }
      }
    
    

    IPersonAssocation has two properties: int? Id and Person.

    And this how the method is called (currentPerson is the method parameter representing the latest data):

       Person originalPerson = GetPersonById(personId.Value);
    
       // update the person (this doesn't touch the associations):
       db.People.ApplyCurrentValues(currentPerson);
    
       // deal with each association in turn
       UpdatePersonAssociation<Name>(originalPerson.Names, currentPerson.Names, db.Names);
       UpdatePersonAssociation<Address>(originalPerson.Addresses, currentPerson.Addresses, db.Addresses);
       UpdatePersonAssociation<Phone>(originalPerson.Phones, currentPerson.Phones, db.Phones);
       UpdatePersonAssociation<Passport>(originalPerson.Passports, currentPerson.Passports, db.Passports);
       UpdatePersonAssociation<Licence>(originalPerson.Licences, currentPerson.Licences, db.Licences);
       UpdatePersonAssociation<IdentityDocument>(originalPerson.IdentityDocuments, currentPerson.IdentityDocuments, db.IdentityDocuments);
    
       // save all changes
       db.SaveChanges();
    
    
    

     

     

    Sunday, July 18, 2010 9:09 PM
  • Hmm interesting question. I recently had the discussion with EF team on this one. Let me give you a short answer on this one. When you bring a modified object graph and retrieve the original object graph from the database and hope that EF can figure out what has changed, then this feature is not supported. As you have seen ApplyCurrentValues and ApplyOriginalValues only work with a single entity. Anyways from what i know they had a choice to either go with Self Tracking entity or to support this scenario of full poco and then comparing object graph. I guess based on the feedback in the community they decided to go down the route of STE because it is really POCO with change tracking feature. However i believe they are considering adding this feature but i don't know. All i can say is I really want this feature supported out of the box for deep object graph comparison.  The more people vote on this feature will determine if this feature can make it to next release.

     

     


    Zeeshan Hirani Entity Framework 4.0 Recipes by Apress
    http://weblogs.asp.net/zeeshanhirani
    Friday, July 23, 2010 8:05 PM
  • Where can people vote for this feature? I guess there's already a connect item for it?

    Sunday, July 25, 2010 10:22 PM
  • hi,

     

    I have the same issue as you have discribed. However when i try the above code i get the following error:

    Collection was modified; enumeration operation may not execute.

     

    Are you able to tell me what i have don wrong?

     

    Sunday, January 15, 2012 4:09 AM
  • I found this to be an easy solution...

    public string SaveParentWithChildren(Guid _userId, Parent parent)
            {
                try
                {
                    using (var context = new Entities())
                    {
                        List<Child> ch = new List<Child>();
                        ch = parent.Children.ToList();
                        parent.Children = null;
    
                        parent.UserId = _userId;
                        context.Parents.Attach(parent);
                       
                        context.ObjectStateManager.ChangeObjectState(parent, StateHelpers.GetEquivalentEntityState(parent.EntState));
    
                        if (ch.Count > 0)
                        {
                            foreach (var child in ch)
                            {
                                child.UserId = _userId;
                                context.Children.Attach(child);
                                context.ObjectStateManager.ChangeObjectState(child, StateHelpers.GetEquivalentEntityState(child.EntState));
                            }
                        }
    
    
                        context.SaveChanges();
                        return parent.Id.ToString();
                    }
                }
                catch (Exception ex)
                {
                    return ex.Message;
                }
    
    
            }

    Thursday, November 15, 2012 6:15 PM