private void SerializeEntityRelationships(XmlWriter xmlWriter, EntityHierarchyEntry entityNode, Stack <string> xmlStack) { if (entityNode.ForwardRelationships != null) { string relTag = XmlConstants.RelationshipConstants.Rel; foreach (RelationshipEntry relationship in entityNode.ForwardRelationships) { using (WriteElementBlock(xmlWriter, relationship.TypeId, relTag, relTag, XmlConstants.Id, xmlStack, false)) { xmlWriter.WriteString(GetInnerText(relationship.ToId)); } } } if (entityNode.ReverseRelationships != null) { foreach (RelationshipEntry relationship in entityNode.ReverseRelationships) { string relTag = XmlConstants.RelationshipConstants.RevRel; using (WriteElementBlock(xmlWriter, relationship.TypeId, relTag, relTag, XmlConstants.Id, xmlStack, true)) { xmlWriter.WriteString(GetInnerText(relationship.FromId)); } } } }
private void SerializeNestedEntities(XmlWriter xmlWriter, EntityHierarchyEntry entityNode, Stack <string> xmlStack) { if (entityNode.Children == null) { return; } // Children grouped by rel type var childrenByType = entityNode.Children .GroupBy(e => new Tuple <Direction, Guid>(e.Direction, e.RelationshipFromParent.TypeId)) .OrderBy(t => t.Key); foreach (var childList in childrenByType) { Direction dir = childList.Key.Item1; Guid relTypeId = childList.Key.Item2; string relTag = dir == Direction.Forward ? XmlConstants.RelationshipConstants.Rel : XmlConstants.RelationshipConstants.RevRel; using (WriteElementBlock(xmlWriter, relTypeId, relTag, relTag, XmlConstants.Id, xmlStack, dir == Direction.Reverse)) { foreach (EntityHierarchyEntry childNode in childList) { SerializeEntity(xmlWriter, childNode, xmlStack); } } } }
/// <summary> /// Handle case if one entity is a component of the other. /// </summary> /// <param name="relType">The relationship type.</param> /// <param name="fromEntity">The 'from' entity.</param> /// <param name="toEntity">The 'to' entity.</param> /// <returns></returns> private bool HandleContainedEntities(RelationshipEntry rel, RelationshipTypeEntry relType, EntityHierarchyEntry fromEntity, EntityHierarchyEntry toEntity) { if (relType == null) { return(false); } // Explicitly do not next certain relationships if (rel.TypeId == Guids.InSolution || rel.TypeId == Guids.IndirectInSolution) { return(false); } // if 'fwdComponent' then parent = FromId // if 'revComponent' then parent = ToId // "Clone action" is the best proxy to determine if something is a subcomponent, while still handling 'custom' reltypes. bool fwdComponent = relType.CloneAction == CloneActionEnum_Enumeration.CloneEntities; bool revComponent = !fwdComponent && relType.ReverseCloneAction == CloneActionEnum_Enumeration.CloneEntities; EntityHierarchyEntry parent = null; EntityHierarchyEntry child = null; if (fwdComponent) { parent = fromEntity; child = toEntity; } else if (revComponent) { parent = toEntity; child = fromEntity; } else { return(false); } if (child.ParentEntity != null) { return(false); // child already assigned to a different parent } if (WouldCauseCycle(parent, child)) { return(false); } if (parent.Children == null) { parent.Children = new List <EntityHierarchyEntry>( ); } parent.Children.Add(child); child.ParentEntity = parent; child.RelationshipFromParent = rel; child.Direction = revComponent ? Direction.Reverse : Direction.Forward; return(true); }
/// <summary> /// Returns true if adding this child to this parent would cause a cycle. /// </summary> /// <param name="parent">The potential parent</param> /// <param name="child">The potential child</param> /// <returns>True if a cycle would result.</returns> private bool WouldCauseCycle(EntityHierarchyEntry parent, EntityHierarchyEntry child) { EntityHierarchyEntry current = parent; while (current != null) { if (current == child) { return(true); } current = current.ParentEntity; } return(false); }
private void SerializeEntity(XmlWriter xmlWriter, EntityHierarchyEntry entityNode, Stack <string> xmlStack) { EntityEntry entity = entityNode.Entity; Guid typeId = entityNode.TypeRelationship?.ToId ?? Guid.Empty; WriteEntityNameComment(xmlWriter, entity); // TODO : HANDLE MISSING TYPE RELATIONSHIP PROPERLY using (WriteElementBlock(xmlWriter, typeId, XmlConstants.EntityConstants.Type, XmlConstants.EntityConstants.Entity, XmlConstants.EntityConstants.TypeId, xmlStack)) { xmlWriter.WriteAttributeString(XmlConstants.Id, XmlConvert.ToString(entity.EntityId)); SerializeEntityFields(xmlWriter, entity, xmlStack); SerializeEntityRelationships(xmlWriter, entityNode, xmlStack); SerializeNestedEntities(xmlWriter, entityNode, xmlStack); } }
/// <summary> /// Serializes the entities. /// </summary> /// <param name="xmlWriter">The XML writer.</param> /// <param name="xmlStack">The XML stack.</param> private void SerializeEntities(XmlWriter xmlWriter, Stack <string> xmlStack) { IEnumerable <EntityEntry> entities = PackageData.Entities; HierarchyBuilder builder = new HierarchyBuilder( ); builder.RelationshipMetadataCallback = PackageData.Metadata.RelationshipTypeCallback; _root = builder.BuildEntityHierarchy(PackageData); ///// // By this point the entire entity graph has been built. Now serialize it ///// using (xmlWriter.WriteElementBlock(XmlConstants.EntityConstants.Entities, xmlStack)) { if (_root.Children != null) { var typeGroups = _root.Children .GroupBy(e => e.TypeRelationship?.ToId) .Select(g => new { TypeId = GuidOrAlias(g?.Key), Group = g }) .OrderBy(g => TypeOrder(g.TypeId)) // move 'solution' to the top .ThenBy(g => g.TypeId); foreach (var group in typeGroups) { using (xmlWriter.WriteElementBlock(XmlConstants.EntityConstants.Group, xmlStack)) { string typeId = group.TypeId; if (typeId != null) { // Note : this is purely for grouping cosmetics, do not use it for reading information. xmlWriter.WriteAttributeString(XmlConstants.EntityConstants.TypeId, typeId); } SerializeEntityList(xmlWriter, group.Group, xmlStack); } } } } }
public EntityHierarchyEntry BuildEntityHierarchy([NotNull] PackageData packageData) { if (packageData == null) { throw new ArgumentNullException(nameof(packageData)); } _root = new EntityHierarchyEntry { Children = new List <EntityHierarchyEntry>( ), ForwardRelationships = new List <RelationshipEntry>( ) }; // Create dictionary of entity nodes if (packageData.Entities != null) { _entities = packageData.Entities.ToDictionary( e => e.EntityId, e => new EntityHierarchyEntry { Entity = e }); } else { _entities = new Dictionary <Guid, EntityHierarchyEntry>( ); } // Walk all relationships // Note: relationships get canonically ordered to ensure deterministic construction of hierarchy foreach (RelationshipEntry rel in packageData.Relationships.OrderBy(r => r.TypeId).ThenBy(r => r.FromId).ThenBy(r => r.ToId)) { EntityHierarchyEntry fromEntity; EntityHierarchyEntry toEntity; _entities.TryGetValue(rel.FromId, out fromEntity); _entities.TryGetValue(rel.ToId, out toEntity); // Handle 'type' relationship if (fromEntity != null && rel.TypeId == Guids.IsOfType && fromEntity.TypeRelationship == null) { fromEntity.TypeRelationship = rel; continue; } RelationshipTypeEntry relType = GetRelationshipMetadata(rel.TypeId); // Detect entities that contain entities if (fromEntity != null && toEntity != null) { if (HandleContainedEntities(rel, relType, fromEntity, toEntity)) { continue; } } // Other relationships - find somewhere to add them. if (fromEntity != null) { if (fromEntity.ForwardRelationships == null) { fromEntity.ForwardRelationships = new List <RelationshipEntry>( ); } fromEntity.ForwardRelationships.Add(rel); continue; } if (toEntity != null) { if (toEntity.ReverseRelationships == null) { toEntity.ReverseRelationships = new List <RelationshipEntry>( ); } toEntity.ReverseRelationships.Add(rel); continue; } // Nowhere else to add them.. _root.ForwardRelationships.Add(rel); } // Gather top tier entities into root entity foreach (var entity in _entities.Values) { if (entity.ParentEntity != null) { continue; } _root.Children.Add(entity); entity.ParentEntity = _root; } return(_root); }