ontehfritz – Application Development and Stuff

November 21, 2008

.Net Entity Framework Updating Objects in N-Tier Environment

Filed under: .Net Entity Framework 3.5 SP1 — Tags: — ontehfritz @ 11:37 am

Oh, Boy. OK, Phew. This was quite the deal to get Objects with navigation properties to update. The issue with updates is this, when using the method:

yourcontext.ApplyPropertyChanges(key.EntitySetName, o);

It only works on scalar properties in your entity object. Not the navigation properties or the reference objects (Foreign Key Relationships). I composed from the very few resources on the entity framework this method in my WCF Service:
Credits to this blog entry for guiding me in the right direction. Check it out.

private void Update(System.Data.Objects.DataClasses.EntityObject o)
    {
        EntityKey key;
        Object originalItem;

        using (CorporateEntities ce = new CorporateEntities())
        {
            key = ce.CreateEntityKey(o.EntityKey.EntitySetName, o);

            if (ce.TryGetObjectByKey(key, out originalItem))
            {
                ce.ApplyPropertyChanges(
                        key.EntitySetName, o);

                foreach (var entityrelationship in ((IEntityWithRelationships)originalItem).RelationshipManager.GetAllRelatedEnds())
                {
                    var oldRef = entityrelationship as EntityReference;

                    if (oldRef != null)
                    {
                        var newRef = ((IEntityWithRelationships)o).RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
                        oldRef.EntityKey = newRef.EntityKey;
                    }
                }
            }

            ce.SaveChanges();
        }
    }

This method takes the new object and updates the old objects EntityKeys. Why do we have to cast the Entity Object to IEntityWithRelationShips? According to documentation on MSDN it implements IEntityWithRelationShips. Check it out Shouldn’t it be available already. Anyway, Once we do this we can retrieve each navigation property or references; then change the old to the new. However, there is a catch here. If You do not change the entity key on the YourObjectReference it will not update it with new reference object. For example on the client:

Stores updateStore = client.GetStoreByID(666);
/*This has no effect as the updateStore.ConceptsReference will not be updated by the line below*/
updateStore.Concepts = client.GetConcept(3);

“Concepts” is the navigation property and seems logical to assign new object to this, but unfortunately it will have no effect on updating because the updateStore.ConceptReference still points to the original entity. So we need to change the reference. In my WCF Service I have created the following method to do this for me. The not so cool part is that I pass in the navigation properties or objects as a separate parameter on the method. This is to abstract the Presentation Layer from having Entity Framework (EF) specific code in it. Remember ideally the presentation layer should only worry about business objects, not specific Data Access Layer (DAL) nuances.

public void UpDateStore(Stores updatedStore, Concepts concept, StoreStatus status, Distributors distributors, POSSystems pos)
    {
        EntityReference c = new EntityReference();
        c.EntityKey = concept.EntityKey;
        updatedStore.ConceptsReference = c;

        EntityReference s = new EntityReference();
        s.EntityKey = status.EntityKey;
        updatedStore.StoreStatusReference = s;

        EntityReference d = new EntityReference();
        d.EntityKey = distributors.EntityKey;
        updatedStore.DistributorsReference = d;

        EntityReference p = new EntityReference();
        p.EntityKey = pos.EntityKey;
        updatedStore.POSSystemsReference = p;

        updatedStore.ModifiedDate = DateTime.Now;
        this.Update(updatedStore);
    }

So now when I call my private update method in the WCF Service it will indeed update!

Client code:

Stores updateStore = client.GetStoreByID(666);
client.UpDateStore(updateStore, client.GetConcept(2), client.GetStat(2), client.GetDistributor(2), client.GetPOSSystem(2));

Hope this helps anyone else struggling with this situation. Better yet if someone can post a more elegant solution that would be great. There is very little information on real world examples of using Entity Framework, especially in N-Tier environment.

Note and Disclaimer: This is test code and may contain bugs and/or improper error handling, it is for academic purposes only, for sharing knowledge, and collaboratively coming up with better solutions. Please always practice proper security, error handling, and coding best practices in production. I am not liable for any code used here; use @ your own risk. Have fun and share the experience and knowledge. Remember, no question is a stupid question, and failure gives us insight.

7 Comments »

  1. Thanks! Gave me Lots of good ideas!

    Comment by Jeff — January 20, 2009 @ 10:07 pm

  2. Thank you for your solution, tht’s exactly what I need.
    Could you tell me why I get an exception while executing the following:
    oldRef.EntityKey = newRef.EntityKey;

    the exception is:
    A referential integrity constraint violation occurred: A property that is a part of referential integrity constraint cannot be changed when the object has a non-temporary key.

    Comment by dan — May 13, 2009 @ 9:02 am

    • I would have to see more of the code. But I will make some assumptions based on what you have posted.

      oldRef.EntityKey = newRef.EntityKey;

      Looks like you are taking an old entity key and changing it to a new one. As you will notice in the UpDateStore method I create a “new” reference object not using an existing one. Assign the entity key to the new reference object, then assign the new reference object to the object I am updating.

      You cannot take an existing entity key and update it to a new one. It is violating a constraint on your database table hence your entity model definition. So use a new object.

      Please post more code to verify what exactly you are doing. TTYL

      Comment by ontehfritz — May 13, 2009 @ 9:15 am

      • here is my code:

        /* …. */
        D updateD = GetDByID(input, input, input);
        // SUPPOSE: this is the Client Side
        EntityReference b = new EntityReference();
        b.EntityKey = b2.EntityKey;
        updateD.BReference = b;

        EntityReference c = new EntityReference();
        c.EntityKey = c2.EntityKey;
        updateD.CReference = c;

        updateD.dAttribute1 = 999999999;

        Update(updateD);

        /* …. */

        private void Update(System.Data.Objects.DataClasses.EntityObject o)
        {
        EntityKey key;
        Object originalItem;

        using (TEST1Entities t2e = new TEST1Entities())
        {
        key = t2e.CreateEntityKey(o.EntityKey.EntitySetName, o);

        if (t2e.TryGetObjectByKey(key, out originalItem))
        {
        t2e.ApplyPropertyChanges(key.EntitySetName, o);

        foreach (var entityrelationship in ((IEntityWithRelationships)originalItem).RelationshipManager.GetAllRelatedEnds())
        {
        var oldRef = (EntityReference)entityrelationship; // as EntityReference; // EntityReference;

        if (oldRef != null)
        {
        var newRef = ((IEntityWithRelationships)o).RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
        oldRef.EntityKey = newRef.EntityKey;
        }
        }
        }

        t2e.SaveChanges();
        }

        }

        private D GetDByID(int A, int B, int C)
        {
        using (TEST1Entities t1e = new TEST1Entities()){
        List dList= (t1e.D.Where(d => d.aId==A && d.bId==B && d.cId==C)).ToList();
        D retD = dList[0];
        t1e.Detach(retD);
        return retD;
        }

        }

        Comment by dan — May 14, 2009 @ 2:28 am

  3. ps. the exception is raised in the Update method, while trying to update the original Item relationships

    Comment by dan — May 14, 2009 @ 3:46 am

  4. sorry…i got the problem: my foreign key was part of my primary key, hence the exception!
    Any idea about solving my issue?

    Comment by dan — May 14, 2009 @ 6:10 am

  5. Is your primary key compound, meaning more than one field makes the primary key? This can also cause issues with the entity framework. Especially if they are a foreign key. What I have done to make life easier is create an additional identity key and make that the primary key. This way the entity framework can use this identity as its identifier, you can still have the foreign keys, but just not as primary key. Also if it is many-to-many relationship between the tables can cause issues as well. You will have to have mapping table to make it work correctly and the foreign keys cannot be part of the primary key.

    If you want the records to be unique by the foreign keys then put a unique constraint on the table with those fields instead of making it the primary key. Then add the identity field as the primary. This should work. Kind of weird but that is what you have to do, to make your tables to play nicely with Entity framework.

    Comment by ontehfritz — May 14, 2009 @ 9:17 am


RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.