Open ghost opened 10 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?
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
var graphDiffer = new GraphDiffer<T>(root);
return graphDiffer.Merge(context, entity);
}
internal class NodeGenerator
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();
}
}
}
Think about adding support for mapping of recursive data structures of non-predetermined depth as requested here.