/// <summary> /// Implements the operator +. /// </summary> /// <param name="a">The instance of this object that will is the left side of the operator.</param> /// <param name="b">The EntityPathComponent instance that is the right side of the operator.</param> /// <returns> /// The result of the operator. /// </returns> public static EntityPath operator +(EntityPath a, EntityPathComponent b) { EntityPath path = a.Clone(); path.Add(b); return(path); }
/// <summary> /// Create a duplicate copy of this entity path and return it. /// </summary> /// <returns>A duplicate of this entity path.</returns> public EntityPath Clone() { EntityPath path = new EntityPath(); path.AddRange(this); return(path); }
/// <summary> /// Initialize a new queued entity for the given access path. /// </summary> /// <param name="entity">The entity that is going to be placed in the queue.</param> /// <param name="path">The initial path used to reach this entity.</param> public QueuedEntity(IEntity entity, EntityPath path) { Entity = entity; ReferencePaths = new List <EntityPath> { path }; ReferencedEntities = new List <KeyValuePair <string, IEntity> >(); UserReferences = new Dictionary <string, Reference>(); }
/// <summary> /// This is used for debug output to display the entity information and the path(s) /// that we took to find it. /// </summary> /// <returns>A string that describes this queued entity.</returns> public override string ToString() { StringBuilder sb = new StringBuilder(); EntityPath primaryPath = ReferencePaths[0]; sb.AppendFormat("{0} {1}", Entity.TypeName, Entity.Guid); foreach (var p in ReferencePaths) { sb.AppendFormat("\n\tPath"); foreach (var e in p) { sb.AppendFormat("\n\t\t{0} {2} {1}", e.Entity.TypeName, e.PropertyName, e.Entity.Guid); } } return(sb.ToString()); }
/// <summary> /// Add a new entity path reference to this existing entity. /// </summary> /// <param name="path">The path that can be used to reach this entity.</param> public void AddReferencePath(EntityPath path) { ReferencePaths.Add(path); }
/// <summary> /// Generate the list of entities that reference this parent entity. These are entities that /// must be created after this entity has been created. /// </summary> /// <param name="parentEntity">The parent entity to find reverse-references to.</param> /// <param name="path">The property path that led us to this final property.</param> /// <param name="exporter">The object that handles filtering during an export process.</param> /// <returns>A list of KeyValuePairs that identify the property names and entites to be followed.</returns> protected List <KeyValuePair <string, IEntity> > FindChildEntities(IEntity parentEntity, EntityPath path, IExporter exporter) { List <KeyValuePair <string, IEntity> > children = new List <KeyValuePair <string, IEntity> >(); var properties = GetEntityProperties(parentEntity); // // Take a stab at any properties that are an ICollection<IEntity> and treat those // as child entities. // foreach (var property in properties) { if (property.PropertyType.GetInterface("IEnumerable") != null && property.PropertyType.GetGenericArguments().Length == 1) { if (typeof(IEntity).IsAssignableFrom(property.PropertyType.GetGenericArguments()[0]) && exporter.ShouldFollowPathProperty(path + new EntityPathComponent(parentEntity, property.Name))) { IEnumerable childEntities = ( IEnumerable )property.GetValue(parentEntity); foreach (IEntity childEntity in childEntities) { children.Add(new KeyValuePair <string, IEntity>(property.Name, childEntity)); } } } } // // We also need to pull in any attribute values. We have to pull attributes as well // since we might not have an actual value for that attribute yet and would need // it to pull the default value and definition. // if (parentEntity is IHasAttributes attributedEntity) { if (attributedEntity.Attributes == null) { attributedEntity.LoadAttributes(RockContext); } foreach (var item in attributedEntity.Attributes) { var attrib = new AttributeService(RockContext).Get(item.Value.Guid); children.Add(new KeyValuePair <string, IEntity>("Attributes", attrib)); var value = new AttributeValueService(RockContext).Queryable() .Where(v => v.AttributeId == attrib.Id && v.EntityId == attributedEntity.Id) .FirstOrDefault(); if (value != null) { children.Add(new KeyValuePair <string, IEntity>("AttributeValues", value)); } } } // // Allow for processors to adjust the list of children. // foreach (var processor in FindEntityProcessors(GetEntityType(parentEntity))) { processor.EvaluateChildEntities(parentEntity, children, this); } return(children); }
/// <summary> /// Find entities that this object references directly. These are entities that must be /// created before this entity can be re-created. /// </summary> /// <param name="parentEntity">The parent entity whose references we need to find.</param> /// <param name="path">The property path that led us to this final property.</param> /// <param name="exporter">The object that handles filtering during an export process.</param> /// <returns>A dictionary that identify the property names and entites to be followed.</returns> protected List <KeyValuePair <string, IEntity> > FindReferencedEntities(IEntity parentEntity, EntityPath path, IExporter exporter) { var references = new List <KeyValuePair <string, IEntity> >(); var properties = GetEntityProperties(parentEntity); // // Take a stab at any properties that end in "Id" and likely reference another // entity, such as a property called "WorkflowId" probably references the Workflow // entity and should be linked by Guid. // foreach (var property in properties) { if (property.Name.EndsWith("Id") && (property.PropertyType == typeof(int) || property.PropertyType == typeof(Nullable <int>))) { var entityProperty = parentEntity.GetType().GetProperty(property.Name.Substring(0, property.Name.Length - 2)); if (entityProperty == null || !typeof(IEntity).IsAssignableFrom(entityProperty.PropertyType)) { continue; } var value = ( IEntity )entityProperty.GetValue(parentEntity); if (exporter.ShouldFollowPathProperty(path + new EntityPathComponent(parentEntity, property.Name))) { references.Add(new KeyValuePair <string, IEntity>(property.Name, value)); } else { references.Add(new KeyValuePair <string, IEntity>(property.Name, null)); } } } // // Allow for processors to adjust the list of children. // foreach (var processor in FindEntityProcessors(GetEntityType(parentEntity))) { processor.EvaluateReferencedEntities(parentEntity, references, this); } return(references); }
/// <summary> /// Adds an entity to the queue list. This provides circular reference checking as /// well as ensuring that proper order is maintained for all entities. /// </summary> /// <param name="entity">The entity that is to be included in the export.</param> /// <param name="path">The entity path that lead to this entity being encoded.</param> /// <param name="entityIsCritical">True if the entity is critical, that is referenced directly.</param> /// <param name="exporter">The exporter that will handle processing for this entity.</param> protected void EnqueueEntity(IEntity entity, EntityPath path, bool entityIsCritical, IExporter exporter) { // // These are system generated rows, we should never try to backup or restore them. // if (entity.TypeName == "Rock.Model.EntityType" || entity.TypeName == "Rock.Model.FieldType") { return; } // // If the entity is already in our path that means we are beginning a circular // reference so we can just ignore this one. // if (path.Where(e => e.Entity.Guid == entity.Guid).Any()) { return; } // // Find the entities that this entity references, in other words entities that must // exist before this one can be created, and queue them up. // var referencedEntities = FindReferencedEntities(entity, path, exporter); foreach (var r in referencedEntities) { if (r.Value != null) { var refPath = path + new EntityPathComponent(entity, r.Key); EnqueueEntity(r.Value, refPath, true, exporter); } } // // If we already know about the entity, add a reference to it and return. // var queuedEntity = Entities.Where(e => e.Entity.Guid == entity.Guid).FirstOrDefault(); if (queuedEntity == null) { queuedEntity = new QueuedEntity(entity, path.Clone()); Entities.Add(queuedEntity); } else { // // We have already visited this entity from the same parent. Not sure why we are here. // if (path.Any() && queuedEntity.ReferencePaths.Where(r => r.Any() && r.Last().Entity.Guid == path.Last().Entity.Guid).Any()) { return; } queuedEntity.AddReferencePath(path.Clone()); } // // Add any new referenced properties/entities that may have been supplied for this // entity, as it's possible that has changed based on the path we took to get here. // foreach (var r in referencedEntities) { if (!queuedEntity.ReferencedEntities.Any(e => e.Key == r.Key && e.Value == r.Value)) { queuedEntity.ReferencedEntities.Add(new KeyValuePair <string, IEntity>(r.Key, r.Value)); } } // // Mark the entity as critical if it's a root entity or is otherwise specified as critical. // if (path.Count == 0 || entityIsCritical || exporter.IsPathCritical(path)) { queuedEntity.IsCritical = true; } // // Mark the entity as requiring a new Guid value if so indicated. // if (exporter.DoesPathNeedNewGuid(path)) { queuedEntity.RequiresNewGuid = true; } // // Find the entities that this entity has as children. This is usually the many side // of a one-to-many reference (such as a Workflow has many WorkflowActions, this would // get a list of the WorkflowActions). // var children = FindChildEntities(entity, path, exporter); children.ForEach(e => EnqueueEntity(e.Value, path + new EntityPathComponent(entity, e.Key), false, exporter)); // // Allow the exporter a chance to add custom reference values. // var userReferences = exporter.GetUserReferencesForPath(entity, path); if (userReferences != null && userReferences.Any()) { foreach (var r in userReferences) { queuedEntity.UserReferences.AddOrReplace(r.Property, r); } } }