Workaround: The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.

 

I started using the ADO .NET Entity Framework in a new ASP .NET Project. I´m always creating my applications in a multi-layered architecture. The Attach/Detach functionality of EF is the instrument to go here. But soon i got a curious exception while adding a newly created entity that does not exists in the database before.

The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.

adress_country

What has happened?
Look at this simple EF diagram. We have a Address and a Country entity type. Country contains all countries in the world. Address can be added, modified, and deleted, while Country only will be referenced.

  • Newly created Address
  • Address references existing Country

 

In a multi-layered environment the method to save the address could look like…

public void AddAdress(Address address)

{

    // Add business rules and validation here…

 

    // Everything is fine, so save the changes now

    using (openorgaEntities context = new openorgaEntities())

    {

        context.AddToAddress(address);

        context.SaveChanges();

    }

}

But the AddAddress method will fail and shows the error message as already shown above.

The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.

 

After googling the web for more information i found the following post in Microsoft forum.

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3945102&SiteID=1

 

The solutions provided in the post are working, but i feel they are all either bad or only working in specific cases.

 

 

The workaround solution:

I wrote an RelationshipManager based helper utility class, that is doing a workaround. Lets take a look to the following illustration.

 

entityhelperutil_flow

The workaround is

  • to remove all relationships first and remember them,
  • then i add or attach the entity object
  • and in the end i attach every existing related entity object and recreate all relationships.

 

Note: I assume here that an Entity already having an EntityKey is an existing entity, while all others are newly created entity objects. This may not be true, in all entity models. So please, check if this is the case also in your model!

 

Implementation of EntityHelperUtil

 

entityhelperutil_cd

The class is having 3 public methods:

  • AddObject replaces the ObjectContext.AddObject method
  • Attach replaces the ObjectContext.Attach method
  • AddObjectOrAttach is combination of both

Additionally the EntityHelperUtil has two methods doing the main work.

  • AddRelationships – Attaches and adds the entity object and every related objects.
  • RemoveRelationships – Removes every relation between the entity objects.

Source of EntityHelperUtil.cs

using System;

using System.ComponentModel;

using System.Collections;

using System.Collections.Generic;

using System.Linq;

using System.Data;

using System.Data.Objects;

using System.Data.Objects.DataClasses;

using System.Text;

using System.Reflection;

 

namespace openorga

{

    /// <summary>

    /// Attach helper for entity framework

    /// </summary>

    public static class EntityHelperUtil

    {

        /// <summary>

        /// Adds or attaches an entity object to the context

        /// </summary>

        public static void AddObjectOrAttach(ObjectContext context, string entitySetName, EntityObject entity)

        {

            if (entity.EntityKey == null)

            {

                AddObject(context, entitySetName, entity);

            }

            else

            {

                Attach(context, entity);

            }

        }

 

        /// <summary>

        /// Adds an entity object to the context

        /// </summary>

        public static void AddObject(ObjectContext context, string entitySetName, IEntityWithRelationships entity)

        {           

            // remove all relations

            List<RelationsShipMapping> map = new List<RelationsShipMapping>();

            RemoveRelationships(map, entity, -1);

 

            // add the entity

            context.AddObject(entitySetName, entity);

 

            // recreate all relationships

            AddRelationships(context, map);

        }

 

        /// <summary>

        /// Attaches an entity object to the context

        /// </summary>

        public static void Attach(ObjectContext context, EntityObject entity)

        {

            // remove all relations

            List<RelationsShipMapping> map = new List<RelationsShipMapping>();

            RemoveRelationships(map, entity, -1);

 

            // attach the entity

            context.Attach(entity);

 

            // recreate all relationships

            AddRelationships(context, map);

        }

 

        /// <summary>

        /// Attaches all related entity object and recreates all relationships

        /// </summary>

        static void AddRelationships(ObjectContext context, List<RelationsShipMapping> map)

        {

            int layer = -1;

 

            // loop through all layers

            do

            {

                layer++;

 

                // return all entity object from a specific layer

                IEnumerable<RelationsShipMapping> rs = from r in map

                                                      where r.Layer == layer

                                                      select r;

 

                // check if there are no further related entity objects

                if (rs.Count<RelationsShipMapping>() == 0)

                    break;

 

                // move through the remembered mappings of the current layer

                foreach (RelationsShipMapping mapping in rs)

                {

                    // check if we need to attach the related entity object

                    IEntityWithKey entityWithKey = mapping.TargetEntity as IEntityWithKey;

                    if (entityWithKey != null && entityWithKey.EntityKey != null)

                        context.Attach(entityWithKey);

 

                    // check if it´s a EntityCollection or EntityReference relationship

                    if (!mapping.IsReference)

                    {

                        // add the related collection entity object

                        MethodInfo mi = typeof(RelationshipManager).GetMethod("GetRelatedCollection").MakeGenericMethod(mapping.TargetEntity.GetType());

                        object col = mi.Invoke(

                            mapping.Entity.RelationshipManager,

                            new object[]{ mapping.RelationshipName, mapping.TargetRole });

                        MethodInfo miAdd = col.GetType().GetMethod("Add");

                        miAdd.Invoke(col, new object[] { mapping.TargetEntity });

 

                    }

                    else

                    {

                        // set the related reference entity object

                        MethodInfo mi = typeof(RelationshipManager).GetMethod("GetRelatedReference").MakeGenericMethod(mapping.TargetEntity.GetType());

 

                        EntityReference er = (EntityReference)mi.Invoke(

                            mapping.Entity.RelationshipManager,

                            new object[]{ mapping.RelationshipName, mapping.TargetRole });

 

                        er.GetType().GetProperty("Value").SetValue(er, mapping.TargetEntity, null);

                    }

                }

            }

            while (true);

 

        }

 

        /// <summary>

        /// Removes all related entity objects

        /// </summary>

        static void RemoveRelationships(List<RelationsShipMapping> map, IEntityWithRelationships entity, int layer)

        {

            // the layer represents the stack in the relationships

            // for example: the related entities to main entity object is layer 0,

            // and the related entites to the related entites is layer 1, etc…

            layer++;

 

            // get a collection of all related conceptual entities.

            IEnumerable<IRelatedEnd> en = entity.RelationshipManager.GetAllRelatedEnds();

 

            foreach (IRelatedEnd end in en)

            {

                // check if the relation is an EntityCollection

                IEnumerable col = end as IEnumerable;

                if (col != null)

                {

                    string colRelationshipName = (string)col.GetType().GetProperty("RelationshipName").GetValue(col, null);

                    string colSourceRoleName = (string)col.GetType().GetProperty("SourceRoleName").GetValue(col, null);

                    string colTargetRoleName = (string)col.GetType().GetProperty("TargetRoleName").GetValue(col, null);

 

                    IEnumerator enu = col.GetEnumerator();

 

                    List<IEntityWithRelationships> mem = new List<IEntityWithRelationships>();

                    while (enu.MoveNext())

                    {

                        IEntityWithRelationships item = (IEntityWithRelationships)enu.Current;

 

                        // check if the relationship is already added the map

                        int alreadyExists = (from c in map

                                            where c.RelationshipName == colRelationshipName && c.TargetRole == colSourceRoleName

                                            select c).Count();

                        if (alreadyExists > 0)

                            continue;

 

                        // remember the relationships and add it to the map

                        RelationsShipMapping mapping = new RelationsShipMapping();

                        mapping.Layer = layer;

                        mapping.RelationshipName = colRelationshipName;

                        mapping.TargetRole = colTargetRoleName;

                        mapping.Entity = entity;

                        mapping.TargetEntity = item;

                        mapping.IsReference = false;

 

                        map.Add(mapping);

 

                        mem.Add(item);

 

                        RemoveRelationships(map, item, layer);

                    }

 

                    // remove the entity collection relationships

                    foreach(IEntityWithRelationships item in mem)

                    {

                        end.Remove(item);

                    }

                }

 

                // check if the relation is an EntityReference

                EntityReference er = end as EntityReference;

                if (er != null)

                {

                    System.Reflection.PropertyInfo pValue = er.GetType().GetProperty("Value");

                    IEntityWithRelationships value = (IEntityWithRelationships)pValue.GetValue(er, null);

                    if (value == null)

                        continue;

 

                    if (er != null)

                    {

                        // check if the reference is already added the map

                        int alreadyExists = (from c in map

                                            where c.RelationshipName == er.RelationshipName && c.TargetRole == er.SourceRoleName

                                            select c).Count();

                        if (alreadyExists > 0)

                            continue;

 

                        // remember the relationships and add it to the map

                        RelationsShipMapping mapping = new RelationsShipMapping();

                        mapping.Layer = layer;

                        mapping.RelationshipName = er.RelationshipName;

                        mapping.TargetRole = er.TargetRoleName;

                        mapping.Entity = entity;

                        mapping.TargetEntity = value;

                        mapping.IsReference = true;

 

                        map.Add(mapping);

 

                        // remove the entity reference relationships

                        pValue.SetValue(er, null, null);

 

                        RemoveRelationships(map, value, layer);

                    }

                }

            }

        }

 

        /// <summary>

        /// Used to remember relationships in the entity object

        /// </summary>

        class RelationsShipMapping

        {

            public int Layer;

            public string RelationshipName;

            public string TargetRole;

            public IEntityWithRelationships Entity;

            public IEntityWithRelationships TargetEntity;

            public bool IsReference;

        }

    }

}

Advertisements

14 thoughts on “Workaround: The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.

  1. Fawad says:

    Can you tell me how i can use this class. I have a Order entity that has an attached sub entity OrderStatus, using your class how will i update my entire entity graph?. Can you provide me with a sample code

  2. Sergey says:

    Frankly, this is damn near crazy for something that’s an obvious design flaw.

  3. Brett says:

    Thank you! I’ve been slamming my head against a wall for two days with this.

  4. Unknown says:

    Excellent solution to a ridiculous problem. Thank you!

  5. Florin says:

    Great job.First real solution that I find to this problem

  6. Kenneth says:

    Damn handy. As someone else put it, "Excellent solution to a ridiculous problem." Let’s hope they get these cases fixed in v2. Sheesh.

  7. Jason says:

    Nice trick I think this will work for me

  8. Javan says:

    Hey… do you have a way to DETACH objects the same way?

  9. Paul says:

    Hi Bernhard,Sincere thanks for this solution. I have been tearing my hair our for almost a full day trying to get this to work. To be honest I am staggered by some of the shortcomings of the EF. It fundamentally doesn’t work in many cases. It’s mind boggling how MS decided that it was fit for release.Cheers.

  10. Andrei says:

    Thanks for solution. Is this bug fixed in EF 4 ?

  11. Daniel says:

    Great!thanks for that.

  12. Nidhi says:

    Thanks a lot. U’ve really saved my life..

  13. mugume david says:

    Haven’t read through all these posts, but I think you are trying to re-invent the wheel. I had the same issue. Check out this link.
    http://geeks.ms/blogs/quintas/archive/2008/02/21/ado-net-entity-framework-attaching-detached-objects-in-short-lived-contexts.aspx

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: