/// <summary> /// Visits the node difference. /// </summary> /// <param name="difference">The difference.</param> /// <returns> /// A sequence of actions that must be performed to upgrade /// from <see cref="IDifference.Source"/> of the specified /// difference to its <see cref="IDifference.Target"/>. /// </returns> protected virtual void VisitNodeDifference(NodeDifference difference) { var any = difference.Target ?? difference.Source; var isInverseOrderUpgrade = Stage == UpgradeStage.Prepare || Stage == UpgradeStage.Cleanup || Stage == UpgradeStage.TemporaryRename; using (OpenActionGroup(string.Format(NodeGroupComment, any.Name))) { if (isInverseOrderUpgrade) { ProcessProperties(difference); if (IsAllowedForCurrentStage(difference)) { ProcessMovement(difference); } } else { if (IsAllowedForCurrentStage(difference)) { ProcessMovement(difference); } ProcessProperties(difference); } } }
/// <summary> /// Determines whether the specified difference allowed for current <see cref="Stage"/>. /// </summary> /// <param name="difference">The difference.</param> /// <returns> /// <see langword="true"/> if the specified difference allowed /// for current <see cref="Stage"/>; otherwise, <see langword="false"/>. /// </returns> /// <exception cref="ArgumentOutOfRangeException"><c>Stage</c> is out of range.</exception> protected virtual bool IsAllowedForCurrentStage(NodeDifference difference) { var isRemoved = (difference.MovementInfo & MovementInfo.Removed) != 0; var isCreated = (difference.MovementInfo & MovementInfo.Created) != 0; var isNameChanged = (difference.MovementInfo & MovementInfo.NameChanged) != 0; var isImmutable = Context.IsImmutable; var isRemoveOnPrepare = isRemoved && !difference.IsRemoveOnCleanup || (isImmutable && !isCreated); switch (Stage) { case UpgradeStage.CleanupData: return(difference.IsDataChanged); case UpgradeStage.Prepare: return(isRemoveOnPrepare); case UpgradeStage.TemporaryRename: return(isNameChanged && IsCyclicRename(difference)); case UpgradeStage.Upgrade: return(!isRemoveOnPrepare && (isCreated || isNameChanged)); case UpgradeStage.CopyData: case UpgradeStage.PostCopyData: return(difference.IsDataChanged); case UpgradeStage.Cleanup: return(isRemoved); default: throw new ArgumentOutOfRangeException("Stage"); } }
/// <summary> /// Gets the property differences for each property of type <see cref="Node"/> or <see cref="NodeCollection"/>. /// </summary> /// <param name="difference">The difference.</param> /// <returns>Property differences set.</returns> protected IEnumerable <NodeDifference> GetPropertyDifferences(NodeDifference difference) { return (difference.PropertyChanges.Values.OfType <NodeDifference>().Union( difference.PropertyChanges.Values.OfType <NodeCollectionDifference>() .SelectMany(collectionDifference => collectionDifference.ItemChanges))); }
/// <summary> /// Process <see cref="NodeDifference.PropertyChanges"/> for specific <see cref="NodeDifference"/>. /// </summary> /// <param name="difference">The difference.</param> protected virtual void ProcessProperties(NodeDifference difference) { var isImmutable = Context.IsImmutable; var isCleanup = Stage == UpgradeStage.Prepare || Stage == UpgradeStage.Cleanup; var any = difference.Target ?? difference.Source; IEnumerable <KeyValuePair <string, Difference> > propertyChanges = difference.PropertyChanges; if (isCleanup) { propertyChanges = propertyChanges.Reverse(); } foreach (var pair in propertyChanges) { var accessor = any.PropertyAccessors[pair.Key]; if (!isCleanup || !isImmutable || IsVolatile(difference, accessor)) { using (CreateContext().Activate()) { Context.Property = pair.Key; Context.IsImmutable = IsImmutable(difference, accessor); if ((difference.MovementInfo & MovementInfo.Removed) != 0) { Context.IsRemoved = true; } var dependencyRootType = GetDependencyRootType(difference, accessor); if (dependencyRootType != null) { Context.DependencyRootType = dependencyRootType; } Visit(pair.Value); } } } }
/// <summary> /// Determines whether is cycle rename detected. /// </summary> /// <param name="difference">The difference.</param> /// <returns> /// <see langword="true"/> if is cycle rename exists; otherwise, <see langword="false"/>. /// </returns> protected virtual bool IsCyclicRename(NodeDifference difference) { if (!difference.IsNameChanged || difference.Source == null || difference.Target == null) { return(false); } var source = difference.Source; var target = difference.Target; if (!source.Nesting.IsNestedToCollection) { return(false); } var collection = source.Parent.GetProperty(source.Nesting.PropertyName) as NodeCollection; return(collection.Contains(target.Name) || Hints.OfType <RenameHint>() .Any(renameHint => renameHint.TargetPath == source.Path)); }
/// <summary> /// Generates <see cref="NodeAction"/> according to <see cref="NodeDifference.MovementInfo"/>. /// </summary> /// <param name="difference">The difference.</param> protected virtual void ProcessMovement(NodeDifference difference) { var source = difference.Source; var target = difference.Target; var isImmutable = Context.IsImmutable; var isChanged = (difference.MovementInfo & MovementInfo.Changed) != 0; var isCreated = (difference.MovementInfo & MovementInfo.Created) != 0; var isRemoved = (difference.MovementInfo & MovementInfo.Removed) != 0 || (isImmutable && difference.HasChanges && !isCreated); var sc = StringComparer.OrdinalIgnoreCase; switch (Stage) { case UpgradeStage.CleanupData: if (difference.IsDataChanged) { Hints.GetHints <UpdateDataHint>(difference.Source) .Where(hint => sc.Compare(hint.SourceTablePath, difference.Source.Path) == 0) .ForEach(hint => AddAction(UpgradeActionType.Regular, new DataAction { DataHint = hint })); Hints.GetHints <DeleteDataHint>(difference.Source) .Where(hint => !hint.PostCopy) .Where(hint => sc.Compare(hint.SourceTablePath, difference.Source.Path) == 0) .ForEach(hint => AddAction(UpgradeActionType.Regular, new DataAction { DataHint = hint })); } break; case UpgradeStage.Prepare: if (isRemoved && !difference.IsRemoveOnCleanup) { if (!Context.IsRemoved || difference.IsDependentOnParent) { AddAction(UpgradeActionType.PreCondition, new RemoveNodeAction { Path = (source ?? target).Path }); } } break; case UpgradeStage.Cleanup: if (!Context.IsRemoved || difference.IsDependentOnParent) { AddAction(UpgradeActionType.PreCondition, new RemoveNodeAction { Path = (source ?? target).Path }); } break; case UpgradeStage.TemporaryRename: RegisterTemporaryRename(source); var temporaryName = GetTemporaryName(source); AddAction(UpgradeActionType.Rename, new MoveNodeAction { Path = source.Path, Parent = source.Parent == null ? string.Empty : source.Parent.Path, Name = temporaryName, Index = null, NewPath = GetPathWithoutName(source) + temporaryName }); break; case UpgradeStage.CopyData: Hints.GetHints <CopyDataHint>(difference.Source) .Where(hint => sc.Compare(hint.SourceTablePath, difference.Source.Path) == 0) .ForEach(hint => AddAction(UpgradeActionType.Regular, new DataAction { DataHint = hint })); break; case UpgradeStage.PostCopyData: if (difference.IsDataChanged) { Hints.GetHints <DeleteDataHint>(difference.Source) .Where(hint => hint.PostCopy) .Where(hint => sc.Compare(hint.SourceTablePath, difference.Source.Path) == 0) .ForEach(hint => AddAction(UpgradeActionType.Regular, new DataAction { DataHint = hint })); } break; case UpgradeStage.Upgrade: if (target == null) { break; } if (isCreated) { var action = new CreateNodeAction { Path = target.Parent == null ? string.Empty : target.Parent.Path, Type = target.GetType(), Name = target.Name, Index = target.Nesting.IsNestedToCollection ? (int?)target.Index : null }; AddAction(UpgradeActionType.PostCondition, action); } else if (isChanged) { var action = new MoveNodeAction { Path = source.Path, Parent = target.Parent == null ? string.Empty : target.Parent.Path, Name = target.Name, Index = target.Nesting.IsNestedToCollection ? (int?)source.Index : null, NewPath = target.Path }; AddAction(UpgradeActionType.PostCondition, action); } break; } }
/// <summary> /// Compares source and target node properties. /// </summary> /// <param name="source">The source.</param> /// <param name="target">The target.</param> /// <param name="difference">The difference.</param> protected virtual void CompareProperties(Node source, Node target, NodeDifference difference) { var any = source ?? target; foreach (var pair in any.PropertyAccessors) { var accessor = pair.Value; if (accessor.IgnoreInComparison) { continue; } var property = accessor.PropertyInfo; using (CreateContext().Activate()) { Context.PropertyAccessor = accessor; object sourceValue = (source == null || !accessor.HasGetter) ? accessor.Default : accessor.Getter.Invoke(source); object targetValue = (target == null || !accessor.HasGetter) ? accessor.Default : accessor.Getter.Invoke(target); object anyValue = sourceValue ?? targetValue; if (anyValue == null) { continue; // Both are null } Difference propertyDifference = null; if (!IsReference(sourceValue, targetValue)) { propertyDifference = Visit(sourceValue, targetValue); } else if (Stage == ComparisonStage.ReferenceComparison) { if (IsIgnored(sourceValue)) { difference.MovementInfo = 0; } else { propertyDifference = GetReferencedPropertyDifference(sourceValue, targetValue, target, property.Name); } } if (propertyDifference == null) { continue; } if (any.Nesting.PropertyInfo != null && accessor.DependencyRootType == any.Nesting.PropertyInfo.PropertyType) { if (propertyDifference is NodeDifference) { ((NodeDifference)propertyDifference).IsDependentOnParent = true; } else if (propertyDifference is NodeCollectionDifference) { ((NodeCollectionDifference)propertyDifference).ItemChanges .ForEach(item => item.IsDependentOnParent = true); } } difference.PropertyChanges.Add(property.Name, propertyDifference); } } }