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.
But the AddAddress method will fail and shows the error message as already shown above.
After googling the web for more information i found the following post in Microsoft forum.
The solutions provided in the post are working, but i feel they are all either bad or only working in specific cases.
I wrote an RelationshipManager based helper utility class, that is doing a workaround. Lets take a look to the following illustration.
Additionally the EntityHelperUtil has two methods doing the main work.
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;
}
}
}