zzzprojects / GraphDiff

GraphDiff is a library that allows the automatic update of a detached graph using Entity Framework code first.
https://entityframework-graphdiff.net/overview
MIT License
333 stars 101 forks source link

Support for recursive mappings of non-predetermined depth? #106

Open ghost opened 10 years ago

ghost commented 10 years ago

Think about adding support for mapping of recursive data structures of non-predetermined depth as requested here.

cherrydev commented 9 years ago

Any clues on where one would start if one were to implement this? Or even a way of specifying a particular maximum depth rather than having to to nested with => expressions ad nausium?

siegeon commented 9 years ago

You would start here, We can determine the mapping of the entities based on their relationships. Then everything else proceeds the same. I have been using a variant of this for some time now in my production code. The only time I ever run into issues is when the graph is extremely massive (20+ entities).

public static T UpdateGraph(this DbContext context, T entity) where T : class, new() { var root = new NodeGenerator().GetNodes(context, entity);

        var graphDiffer = new GraphDiffer<T>(root);

        return graphDiffer.Merge(context, entity);
    }

internal class NodeGenerator { private readonly List _visitedEntities = new List(); private GraphNode _currentMember; private string _relationShip = "";

    public GraphNode GetNodes(DbContext context, T entity)
    {
        var initialNode = new GraphNode();
        _currentMember = initialNode;
        _visitedEntities.Add(entity.GetHashCode());
        WalkTree(context, entity);

        return initialNode;
    }

    private void WalkTree<TEntity>(DbContext context, TEntity entity)
    {
        foreach (NavigationProperty navigationProperty in context.GetNavigationPropertiesForType(typeof (TEntity)))
        {
            PropertyInfo entityProperty = entity.GetType().GetProperties().First(pi => navigationProperty.Name == pi.Name);
            dynamic value = entityProperty.GetValue(entity, null);
            if (value == null) continue;

            if(_visitedEntities.Contains(entityProperty.GetHashCode())) continue;
            _visitedEntities.Add(entityProperty.GetHashCode());

            DetermineRelationShip(navigationProperty);
            GraphNode newMember = CreateNewMember(entityProperty);
            if (newMember == null) continue;

            _currentMember.Members.Push(newMember);
            _currentMember = newMember;

            if (entityProperty.PropertyType.IsCollectionType(typeof(IEnumerable<>)))
            {               
                foreach (var collectionEntity in value)
                {
                    WalkTree(context, collectionEntity);
                }
            }
            else
            {
                WalkTree(context, value);
            }

            _currentMember = _currentMember.Parent;
        }
    }

    private GraphNode CreateNewMember(PropertyInfo accessor)
    {
        GraphNode newMember;
        switch (_relationShip)
        {
            case "OwnedEntity":
                newMember = new OwnedEntityGraphNode(_currentMember, accessor);
                break;
            case "AssociatedEntity":
                newMember = new AssociatedEntityGraphNode(_currentMember, accessor);
                break;
            case "OwnedCollection":
                newMember = new CollectionGraphNode(_currentMember, accessor, true);
                break;
            case "AssociatedCollection":
                newMember = new CollectionGraphNode(_currentMember, accessor, false);
                break;
            default:
                newMember = null;
                break;
        }
        return newMember;
    }

    private void DetermineRelationShip(NavigationProperty property)
    {
        switch (property.FromEndMember.RelationshipMultiplicity)
        {
            case RelationshipMultiplicity.ZeroOrOne:
                switch (property.ToEndMember.RelationshipMultiplicity)
                {
                    case RelationshipMultiplicity.ZeroOrOne:
                        _relationShip = "AssociatedEntity";
                        break;
                    case RelationshipMultiplicity.One:
                        _relationShip = "Skip";
                        break;
                    case RelationshipMultiplicity.Many:
                        _relationShip = "AssociatedCollection";
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
                break;
            case RelationshipMultiplicity.One:
                switch (property.ToEndMember.RelationshipMultiplicity)
                {
                    case RelationshipMultiplicity.ZeroOrOne:
                        _relationShip = "OwnedEntity";
                        break;
                    case RelationshipMultiplicity.One:
                        _relationShip = "Skip";
                        break;
                    case RelationshipMultiplicity.Many:
                        _relationShip = "OwnedCollection";
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
                break;
            case RelationshipMultiplicity.Many:
                switch (property.ToEndMember.RelationshipMultiplicity)
                {
                    case RelationshipMultiplicity.ZeroOrOne:
                        _relationShip = "Skip";
                        break;
                    case RelationshipMultiplicity.One:
                        _relationShip = "Skip";
                        break;
                    case RelationshipMultiplicity.Many:
                        _relationShip = "Skip";
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }
}