private static MergerConditions[] GetMergerPropertyMap(Type type) { if (type.GetCustomAttribute(typeof(MergableAttribute), true) == null) { throw new ObjectGraphMergerException($"Object type {type.Name} is not marked with the Mergable attribute."); } MergerConditions[] result = null; if (!knownMergerPropertyMaps.TryGetValue(type, out result)) { // Generate the map for this type and cache it List <MergerConditions> conditions = new List <MergerConditions>(); var mergableProperties = type.GetProperties().Where(p => p.CanRead && p.CanWrite); foreach (var prop in mergableProperties) { var policyAttrib = prop.GetCustomAttribute <MergePolicyAttribute>(true); var policy = policyAttrib?.Policy ?? MergePolicy.Default; if (policy != MergePolicy.Ignore) { var condition = new MergerConditions { Policy = policy }; condition.EquivalentValues = ParseEquivalentValues(policyAttrib?.EquivalentValues); if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) && prop.PropertyType != typeof(string)) { condition.IsCollection = true; // Determine the class type contained by this collection var collectionType = prop.PropertyType; if (collectionType.IsGenericType) { condition.CollectionInnerType = collectionType.GenericTypeArguments.First(); } else { condition.CollectionInnerType = collectionType.GetElementType(); } condition.CollectionIdentifierPropertyName = condition.CollectionInnerType?.GetCustomAttribute <MergableAttribute>(true)?.CollectionIdentifier; if (!string.IsNullOrEmpty(condition.CollectionIdentifierPropertyName)) { condition.CollectionIdentifierProperty = condition.CollectionInnerType?.GetProperty(condition.CollectionIdentifierPropertyName); } condition.CollapseSingleItemMatchingName = condition.CollectionInnerType?.GetCustomAttribute <MergableAttribute>(true)?.CollapseSingleItemMatchingProperty; if (!string.IsNullOrEmpty(condition.CollapseSingleItemMatchingName)) { condition.CollapseSingleObjectMatchingProperty = condition.CollectionInnerType?.GetProperty(condition.CollapseSingleItemMatchingName); } } condition.IsSimpleType = IsSimple(prop.PropertyType); condition.Property = prop; conditions.Add(condition); } } result = conditions.ToArray(); knownMergerPropertyMaps.Add(type, result); } return(result); }
private object CalculateMergedValueForMapping(MergerConditions mapping, IEnumerable <object> values) { if (!mapping.IsSimpleType) { throw new ObjectGraphMergerException("Attempted to calculate merged value for a non-value type map."); } switch (mapping.Policy) { case MergePolicy.EqualOrNull: case MergePolicy.Default: { // See if we need to do any mapping of values to their replacements. if (mapping.EquivalentValues.Any()) { object[] knownValues = values.ToArray(); for (int i = 0; i < knownValues.Length; i++) { object replacementValue; if (mapping.EquivalentValues.TryGetValue(knownValues[i], out replacementValue)) { knownValues[i] = replacementValue; } } // Replace the enumerable collection with our modified version values = knownValues; } object knownValue = values.FirstOrDefault(x => x != null); if (values.Any(x => x != null && !x.Equals(knownValue))) { if (IsSimple(knownValue.GetType())) { Console.WriteLine($"Unable to merge values for {mapping.Property.DeclaringType.Name}.{mapping.Property.Name} because values are not equal or null. Values: {values.Select(x => x.ToString()).ComponentsJoinedByString(",")}."); return($"MERGE INCONSISTENCY: {values.Select(x => x.ToString()).ComponentsJoinedByString(",")}"); } else { throw new InvalidOperationException($"Unable to merge values for {mapping.Property.DeclaringType.Name}.{mapping.Property.Name} because values are not equal or null. Values: {values.Select(x => x.ToString()).ComponentsJoinedByString(",")}."); } } return(knownValue); } case MergePolicy.PreferGreaterValue: return(values.OrderByDescending(x => x).FirstOrDefault()); case MergePolicy.PreferLesserValue: return(values.OrderBy(x => x).FirstOrDefault()); default: throw new NotImplementedException($"Unsupported merge policy value: {mapping.Policy}."); } }
private IEnumerable <object> DedupeMembersInCollection(MergerConditions mapping, IEnumerable <NodeToMerge> members) { if (!members.Any()) { return(new object[0]); } Dictionary <object, object> equivelentKeyValues = new Dictionary <object, object>(); var policyAttributeOnIdentifier = mapping?.CollectionIdentifierProperty?.GetCustomAttribute <MergePolicyAttribute>(); if (null != policyAttributeOnIdentifier) { equivelentKeyValues = ParseEquivalentValues(policyAttributeOnIdentifier.EquivalentValues); } if (null == mapping.CollectionIdentifierProperty) { throw new ObjectGraphMergerException($"Missing a collection identifier for class {mapping.CollectionInnerType.Name} referenced from {mapping.Property.DeclaringType.Name}.{mapping.Property.Name}."); } Dictionary <string, List <NodeToMerge> > uniqueMembers = new Dictionary <string, List <NodeToMerge> >(); // First pass, organize everything by the collapse single object matching property, if it exists Dictionary <string, List <NodeToMerge> > matchingMembers = new Dictionary <string, List <NodeToMerge> >(); foreach (var obj in members) { if (mapping.CollapseSingleObjectMatchingProperty != null) { string collapsingKey = (string)mapping.CollapseSingleObjectMatchingProperty.GetValue(obj.Node); if (!string.IsNullOrEmpty(collapsingKey)) { matchingMembers.AddToList(collapsingKey, obj); } } else { string key = (string)mapping.CollectionIdentifierProperty.GetValue(obj.Node); object newKey; if (equivelentKeyValues != null && equivelentKeyValues.TryGetValue(key, out newKey)) { key = (string)newKey; } matchingMembers.AddToList(key, obj); } } foreach (var match in matchingMembers) { // Special case, the same object is defined in two different sources only once. if (match.Value.Count == 2 && match.Value[0].Source != match.Value[1].Source) { // Single instance of a member, with different sources, means we should treat them as a single mergable member uniqueMembers.AddToList(match.Key, match.Value[0], match.Value[1]); } else { // Reindex these nodes using their element identifier instead of the collapsing element identifier foreach (var matchedNode in match.Value) { string key = (string)mapping.CollectionIdentifierProperty.GetValue(matchedNode.Node); uniqueMembers.AddToList(key, matchedNode); } } } // Merge any elements where a duplicate key exists foreach (var key in uniqueMembers) { if (key.Value.Count > 1) { var mergedNode = MergeNodes(key.Value.ToArray()); key.Value.Clear(); key.Value.Add(new NodeToMerge { Source = "merger", Node = mergedNode }); } } // Return the single value from each key member return(from m in uniqueMembers select m.Value.Single().Node); }