1

I've written a method to recursively detaching my objects in entity framework. Actually it works, but the problem is, after execute this method, the one to many properties (the Collections of the object) are removed. Below my Code:

public void DetachRec(object objectToDetach)
{
  DetachRec(objectToDetach, new HashSet<object>());
}

// context is my ObjectContext
private void DetachRec(object objectToDetach, HashSet<object> visitedObjects)
{
  visitedObjects.Add(objectToDetach);

  if (objectToDetach == null)
  {
    return;
  }
  Type type = objectToDetach.GetType();
  PropertyInfo[] propertyInfos = type.GetProperties();
  foreach (PropertyInfo propertyInfo in propertyInfos)
  {
    // on to many
    if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))
    {

      if (context.Entry(objectToDetach).Collection(propertyInfo.Name).IsLoaded)
      {

        var propValue = (IEnumerable) propertyInfo.GetValue(objectToDetach, null);

        if (propValue != null)
        {
          var subcontext = new List<object>();

          foreach (var subObject in propValue)
          {
            subcontext.Add(subObject);
          }
          foreach (var subObject in subcontext)
          {
            if (!visitedObjects.Contains(subObject))
            {
              context.DetachRecursive(subObject, visitedObjects);
            }
          }
        }
      }
    }
    // (many to one)
    else if (propertyInfo.PropertyType.Assembly == type.Assembly)
    {
        //the part to detach many-to-one objects
    }
  }

  context.Detach(objectToDetach);
}
Jimy Weiss
  • 568
  • 1
  • 9
  • 25
  • When you say the collections "are removed", what does that mean? Does that mean the property returns null? – JuanR Mar 31 '17 at 17:32
  • 1
    Please read http://stackoverflow.com/a/7693732/430661 and see if it applies. – Alex Paven Mar 31 '17 at 17:40
  • @Juan, yes it means, the property returns null. – Jimy Weiss Mar 31 '17 at 17:48
  • Reload after detaching – T.S. Mar 31 '17 at 17:55
  • @T.S. can you supplement my code to see what do you mean? – Jimy Weiss Mar 31 '17 at 18:11
  • `context.Refresh(....)` - research how it can reverse your changes – T.S. Mar 31 '17 at 18:18
  • @T.S. why reverse my changes, I don't explicitly set the property to null. It happens by executing context.Detach(objectToDetach). – Jimy Weiss Mar 31 '17 at 18:22
  • Of course Ivan's answer is brilliant as usual, but I'm curious, what's the use case for this? Can't you fetch the entities using `Include` and `AsNoTracking`? I wonder why entities would first need to be attached and then detached for any subsequent processing. Never needed it so far, but that may not mean much. – Gert Arnold Apr 01 '17 at 18:00

1 Answers1

3

Before detaching the collection elements, you could store the collection value and set the collection property to null. This way you'll prevent EF from removing the collection elements when the elements and/or the parent entity is detached. At the end of the process, you will simply restore those values back.

Start by adding the following using to the source code file:

using System.Data.Entity.Infrastructure;

Change the public method implementation as follows:

public void DetachRec(object objectToDetach)
{
    var visitedObjects = new HashSet<object>();
    var collectionsToRestore = new List<Tuple<DbCollectionEntry, object>>();
    DetachRec(objectToDetach, visitedObjects, collectionsToRestore);
    foreach (var item in collectionsToRestore)
        item.Item1.CurrentValue = item.Item2;
}

the private recursive method signature to:

private void DetachRec(object objectToDetach, HashSet<object> visitedObjects, List<DbCollectionEntry, object>> collectionsToRestore)

and the one to many if block body to:

var collectionEntry = context.Entry(objectToDetach).Collection(propertyInfo.Name);
if (collectionEntry.IsLoaded)
{
    var collection = collectionEntry.CurrentValue;
    if (collection != null)
    {
        collectionsToRestore.Add(Tuple.Create(collectionEntry, collection));
        collectionEntry.CurrentValue = null;
        foreach (var item in (IEnumerable)collection)
        {
            if (!visitedObjects.Contains(item))
            {
                DetachRec(item, visitedObjects, collectionsToRestore);
            }
        }
    }
}

and you are done.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343