/// <summary> /// Creates a new instance of this class. /// </summary> /// <param name="result">The comparison's computed result.</param> /// <param name="property1">Property information for the property in the first object.</param> /// <param name="value1">Value found in the property of the first object.</param> /// <param name="property2">Property information for the property in the second object.</param> /// <param name="value2">Value found in the property of the second object.</param> /// <param name="mappingUsed">Mapping information used during the comparison operation.</param> /// <param name="exception">Exception raised during the comparison operation, if any.</param> internal PropertyComparisonResult(ComparisonResult result, PropertyComparisonInfo property1, object value1, PropertyComparisonInfo property2 = null, object value2 = null, PropertyMap mappingUsed = null, System.Exception exception = null) { Result = result; Property1 = property1; Property2 = property2; Value1 = value1; Value2 = value2; MapUsed = mappingUsed; Exception = exception; }
/// <summary> /// Configures the property comparison information of the source property to ignore the /// specified property during the comparison routine against objects of type /// <see cref="TDestination"/>. /// </summary> /// <typeparam name="TSourceProperty">The type of the source property.</typeparam> /// <param name="sourcePropExpr">Source property lambda expression.</param> /// <returns>This configuration object to enable fluent syntax.</returns> public ComparerConfiguration <TSource, TDestination> IgnoreProperty <TSourceProperty>( Expression <Func <TSource, TSourceProperty> > sourcePropExpr ) { PropertyInfo piSource = ExpressionHelper.GetPropertyInfo(sourcePropExpr, nameof(sourcePropExpr)); PropertyComparisonInfo pci = TypeInfo1.Properties[piSource.Name]; //Ignore only for the specified data type. PropertyMap map = new PropertyMap( Type2, PropertyMapOperation.IgnoreProperty ); pci.Maps.Replace(map); return(this); }
/// <summary> /// Creates a copy of this object. /// </summary> /// <param name="clonePropertyMaps">A Boolean value that indicates if property maps should /// be cloned as well.</param> /// <returns>A type information object that contains the same information as this object.</returns> internal TypeInfo Clone(bool clonePropertyMaps) { TypeInfo ti = new TypeInfo(DataType, PropertyMapsIgnored); foreach (PropertyComparisonInfo pci in Properties) { PropertyComparisonInfo newPci = new PropertyComparisonInfo(pci.PropertyDescriptor, pci.IgnoreProperty); if (!PropertyMapsIgnored && pci.Maps.Count > 0 && clonePropertyMaps) { foreach (PropertyMap pm in pci.Maps) { newPci.Maps.Add(pm); } } ti.Properties.Add(newPci); } return(ti); }
/// <summary> /// Creates and returns a list of <see cref="PropertyComparisonInfo"/> objects from a list of /// <see cref="PropertyDescriptor"/> objects. /// </summary> /// <param name="pdColl">The collection of property descriptors.</param> /// <param name="ignorePropertyMappings">A Boolean value that indicates if property /// mappings defined through attributes should be ignored.</param> /// <returns>An enumeration that lists the created <see cref="PropertyComparisonInfo"/> objects.</returns> private static IEnumerable <PropertyComparisonInfo> ObtainPropertyInfos(PropertyDescriptorCollection pdColl, bool ignorePropertyMappings) { foreach (PropertyDescriptor pd in pdColl) { IgnoreForComparisonAttribute ignoreAtt = pd.Attributes.OfType <IgnoreForComparisonAttribute>().FirstOrDefault(); PropertyComparisonInfo pci = new PropertyComparisonInfo(pd, ignoreAtt?.IgnoreOptions ?? IgnorePropertyOptions.DoNotIgnore); if (!ignorePropertyMappings) { //Obtain maps. foreach (PropertyMapAttribute attribute in pd.Attributes.OfType <PropertyMapAttribute>()) { if (attribute == null) { continue; } pci.Maps.Add(attribute.PropertyMap); } } yield return(pci); } }
/// <summary> /// Creates a <see cref="TypeInfo"/> object with the necessary property information to /// compare objects of the specified type. /// </summary> /// <param name="type">The data type whose type information is requested.</param> /// <param name="ignorePropertyMappings">A Boolean value that indicates if property /// mappings defined through attributes should be ignored. If the argument is not /// provided, then by default the property mappings are included.</param> /// <returns>A <see cref="TypeInfo"/> object that can be used to compare properties.</returns> internal static TypeInfo BuildTypeInformation(Type type, bool ignorePropertyMappings = false) { TypeInfo ti = new TypeInfo(type, ignorePropertyMappings); #if NET461 //Obtain property map and ignore property data from MetadataTypeAttribute, if present. PropertyComparisonInfoCollection metadataOnlyPropertyInfos = new PropertyComparisonInfoCollection(); MetadataTypeAttribute att = type.GetCustomAttribute <MetadataTypeAttribute>(); if (att != null) { foreach (PropertyComparisonInfo pi in ObtainPropertyInfos(TypeDescriptor.GetProperties(att.MetadataClassType), ignorePropertyMappings)) { metadataOnlyPropertyInfos.Add(pi); } } #endif //Now process regular property descriptors. foreach (PropertyComparisonInfo pci in ObtainPropertyInfos(TypeDescriptor.GetProperties(type), ignorePropertyMappings)) { #if NET461 if (!ignorePropertyMappings && metadataOnlyPropertyInfos.Contains(pci.Name)) { PropertyComparisonInfo mpci = metadataOnlyPropertyInfos[pci.Name]; //Combine the IgnoreProperty values. pci.IgnoreProperty |= mpci.IgnoreProperty; //Merge the PropertyMap objects. foreach (PropertyMap pm in mpci.Maps) { if (pci.Maps.Contains(pm.TargetType)) { continue; } pci.Maps.Add(pm); } } #endif ti.Properties.Add(pci); } return(ti); }
/// <summary> /// Compares the property values of the first object against the property values of the /// second object according to the preset property mapping rules between the two object /// data types. /// </summary> /// <param name="object1">The first object to be compared against a second object.</param> /// <param name="object2">The second object of the comparison operation.</param> /// <param name="results">If provided, it will be used to collect the comparison results.</param> /// <returns>A Boolean value with the summarized result of the comparison. True if any /// property values were deemed different; false if all property values turned out equal.</returns> /// <exception cref="ArgumentNullException">Thrown if either object is null.</exception> /// <exception cref="ArgumentException">Thrown if either object is not of the expected /// data type.</exception> /// <exception cref="InvalidOperationException">Thrown if both objects are really the same /// object.</exception> public bool Compare(object object1, object object2, ICollection <PropertyComparisonResult> results = null) { #region Argument Validation Guard.RequiredArgument(object1, nameof(object1)); Guard.RequiredArgument(object2, nameof(object2)); Guard.ArgumentCondition( () => object1.GetType() == Type1, nameof(object1), $"The provided object is not of the expected type ({Type1})." ); Guard.ArgumentCondition( () => object2.GetType() == Type2, nameof(object2), $"The provided object is not of the expected type ({Type2})." ); Guard.Condition(() => !Object.ReferenceEquals(object1, object2), "The objects to compare must be different."); #endregion bool isDifferent = false; foreach (PropertyComparisonInfo pci1 in _typeInfo1.Properties) { ComparisonResult result = ComparisonResult.Undefined; //Obtain the PropertyMapping for this propertyInfo. //If none, map by property name. PropertyMap mapToUse = null; if (pci1.Maps.Contains(Type2)) { mapToUse = pci1.Maps[Type2]; } object val1 = null; object val2 = null; PropertyComparisonInfo pci2 = null; System.Exception catchedException = null; //Ignore the property if no mapping exists and is being ignored for type 2, //or mapping exists and it states the property must be ignored. bool propertyIgnored = ((pci1.IgnoreProperty & IgnorePropertyOptions.IgnoreForSelf) == IgnorePropertyOptions.IgnoreForSelf && Type1 == Type2) || ((pci1.IgnoreProperty & IgnorePropertyOptions.IgnoreForOthers) == IgnorePropertyOptions.IgnoreForOthers && Type1 != Type2); if ((mapToUse == null && propertyIgnored) || (mapToUse?.Operation == PropertyMapOperation.IgnoreProperty)) { result |= ComparisonResult.PropertyIgnored; } else { string prop2Name = mapToUse?.TargetProperty ?? pci1.Name; //Get the property value of the first object. val1 = pci1.GetValue(object1); if (_typeInfo2.Properties.Contains(prop2Name)) { //Get the property value of the second object. pci2 = _typeInfo2.Properties[prop2Name]; val2 = pci2.GetValue(object2); //Determine any base type to cover for T? vs T or vice versa. Type p1BaseType = GetNullableUnderlyingType(pci1.PropertyType) ?? pci1.PropertyType; Type p2BaseType = GetNullableUnderlyingType(pci2.PropertyType) ?? pci2.PropertyType; //Determine the comparer to use. IComparer comparer = null; if ((mapToUse?.ForceStringValue ?? false) || p1BaseType != p2BaseType) { comparer = ResolveComparerForType(typeof(string)); try { val1 = ConvertPropertyValueToString(val1, mapToUse?.FormatString); val2 = ConvertPropertyValueToString(val2, mapToUse?.TargetFormatString); } catch (System.Exception ex) { result |= ComparisonResult.StringCoercionException; catchedException = ex; } result |= ComparisonResult.StringCoercion; } else { comparer = ResolveComparerForType(p1BaseType); } if (catchedException == null) { try { int comp = comparer.Compare(val1, val2); if (comp < 0) { result |= ComparisonResult.LessThan; } else if (comp > 0) { result |= ComparisonResult.GreaterThan; } else { result |= ComparisonResult.Equal; } } catch (System.ArgumentException) { //Property type does not implement IComparable and there is no comparer //registered for the data type. result |= ComparisonResult.NoComparer; //So try to at least find out if it is equal or not. try { if (Object.Equals(val1, val2)) { result |= ComparisonResult.Equal; } else { result |= ComparisonResult.NotEqual; } } catch (System.Exception ex) { result |= ComparisonResult.ComparisonException; catchedException = ex; } } catch (System.Exception ex) { result |= ComparisonResult.ComparisonException; catchedException = ex; } } } else { //We are done here since there is no matching property to compare against. result |= ComparisonResult.PropertyNotFound; } } PropertyComparisonResult pcr = new PropertyComparisonResult(result, pci1, val1, pci2, val2, mapToUse, catchedException); results?.Add(pcr); isDifferent = isDifferent || ((result & ComparisonResult.NotEqual) == ComparisonResult.NotEqual); } return(isDifferent); }