Ejemplo n.º 1
0
        /// <summary>
        /// This will merge two object instances and return the newer.
        /// </summary>
        /// <typeparam name="TValue">The data type of the objects to merge.</typeparam>
        /// <param name="oldValue">The old value.</param>
        /// <param name="newValue">The new value.</param>
        /// <param name="nonDefaultMergeCriteria">Any non-default criteria for evaluating the correct new value.</param>
        /// <param name="context">The context on which an instance of <see cref="IMergeCriteria"/> can make a decision.</param>
        /// <param name="breadcrumb">The breadcrumb of the current object path.</param>
        /// <returns>The merged object instances.</returns>
        private TValue MergeObjects <TValue>(TValue oldValue, TValue newValue,
                                             Dictionary <string, IMergeCriteria> nonDefaultMergeCriteria,
                                             ContextRoot context = null,
                                             string breadcrumb   = null)
        {
            Type valueType = typeof(TValue);

            return((TValue)MergeObjects(valueType, oldValue, newValue, nonDefaultMergeCriteria, context, breadcrumb));
        }
Ejemplo n.º 2
0
        /// <summary>
        /// This merges a history of an object graph into a single object instance of type <typeparamref name="T"/>
        /// that contains the most recent data.
        /// </summary>
        /// <param name="history">The history of object data to merge</param>
        /// <param name="historyComparer">The comparer instance to use for sorting the history before merge. Will
        /// default to the type's default comparer if none specified.</param>
        /// <param name="nonDefaultMergeCriteria">Any object/property-specific criteria where by to determine
        /// when to overwrite the value when evaluating the property's history.</param>
        /// <returns>A single object instance containing the most recent data.</returns>
        /// <typeparam name="T">The type of object to merge.</typeparam>
        public T Merge <T>(ICollection <T> history, IComparer <T> historyComparer = null,
                           ICollection <IMergeCriteria> nonDefaultMergeCriteria   = null)
        {
            T value = Activator.CreateInstance <T>();

            try
            {
                if (history == null || history.Count < 1)
                {
                    // return the default instance
                    return(value);
                }

                // build a dictionary from the non-default ruleset
                var mergeCriteria = new Dictionary <string, IMergeCriteria>();
                if (nonDefaultMergeCriteria != null)
                {
                    mergeCriteria = nonDefaultMergeCriteria.ToDictionary(c => c.ActivateAt, c => c);
                }

                if (history.Count == 1)
                {
                    // there's only the one item to "merge"
                    using (IEnumerator <T> enumerator = history.GetEnumerator())
                    {
                        enumerator.MoveNext();
                        return(enumerator.Current);
                    }
                }

                // sort the history before attempting a merge
                List <T> sortedHistory = SortHistory(history, historyComparer);

                // cycle through all items up to the last one
                value = sortedHistory[0];
                for (int i = 1; i < sortedHistory.Count; i++)   // start at 1 as that's the first 'version'
                {
                    // perform merge
                    T   newValue = sortedHistory[i];
                    var context  = new ContextRoot(value, newValue);
                    value = MergeObjects(value, newValue, mergeCriteria, context);
                }
            }
            catch (Exception e)
            {
                _logger.Error(e, "There was a problem trying to merge an Model history.");
                throw;
            }

            return(value);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// This will merge two object instances and return the newer.
        /// </summary>
        /// <param name="valueType">The type of the value being merged.</param>
        /// <param name="oldValue">The old value.</param>
        /// <param name="newValue">The new value.</param>
        /// <param name="nonDefaultMergeCriteria">Any non-default criteria for evaluating the correct new value.</param>
        /// <param name="context">The context on which an instance of <see cref="IMergeCriteria"/> can make a decision.</param>
        /// <param name="breadcrumb">The breadcrumb of the current object path.</param>
        /// <returns>The merged object instances.</returns>
        private object MergeObjects(Type valueType,
                                    object oldValue, object newValue,
                                    Dictionary <string, IMergeCriteria> nonDefaultMergeCriteria,
                                    ContextRoot context = null,
                                    string breadcrumb   = null)
        {
            if (string.IsNullOrWhiteSpace(breadcrumb))
            {
                breadcrumb = valueType.Name;
            }
            _logger.Verbose($"Merging at [{breadcrumb}]...");

            // are we dealing with a simple data type
            Type underlyingType;
            Type currentType = GetType(oldValue, newValue);

            if (currentType.IsNullable(out underlyingType))
            {
                // unbox the type if needed
                currentType = underlyingType;
            }

            if (currentType.IsBaseType() ||
                currentType.IsNullable() ||
                currentType.IsByteArray())
            {
                // perform the merge
                var    mergeCriteria = GetMergeCriteria(breadcrumb, nonDefaultMergeCriteria);
                object mergedValue   = mergeCriteria.DoWrite(oldValue, newValue, context);
                return(mergedValue);
            }

            // TODO: deal with enumerables (the call to IsBaseType() above already checks for string)

            // we have a more complex object, pull out the properties
            PropertyInfo[] properties = currentType.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
            object         merged     = Activator.CreateInstance(valueType);

            foreach (PropertyInfo property in properties)
            {
                // skip anything non-mergable
                if (!property.CanRead || !property.CanWrite)
                {
                    continue;
                }

                // get the values to merge
                string propertyBreadcrumb = breadcrumb.Append(property.Name);
                object oldPropertyValue   = oldValue != null?property.GetValue(oldValue) : null;

                object newPropertyValue = newValue != null?property.GetValue(newValue) : null;

                // do we need to merge at all
                if (oldPropertyValue == null && newPropertyValue == null)
                {
                    continue;
                }

                // we have a value, perform a merge
                object mergedValue = null;
                if (nonDefaultMergeCriteria.ContainsKey(propertyBreadcrumb))
                {
                    // use the write criteria to do the merge
                    var mergeCriteria = GetMergeCriteria(propertyBreadcrumb, nonDefaultMergeCriteria);
                    mergedValue = mergeCriteria.DoWrite(oldPropertyValue, newPropertyValue, context);
                }
                else
                {
                    // do a deep merge
                    mergedValue = MergeObjects(property.PropertyType,
                                               oldPropertyValue,
                                               newPropertyValue,
                                               nonDefaultMergeCriteria,
                                               context,
                                               propertyBreadcrumb);
                }

                _logger.Verbose($"Setting property {propertyBreadcrumb} with: {mergedValue}");
                property.SetValue(merged, mergedValue);
            }

            return(merged);
        }