using System.ComponentModel.DataAnnotations.Schema; using System.Reflection; using System.Text.RegularExpressions; using YLErp.DBModels.Attributes; namespace YLErp.Helpers { public static class DataChangeHelper { private static readonly Dictionary _propertyCaches; static DataChangeHelper() { _propertyCaches = new Dictionary(); } const double TooSmallValue = 1e-5; public static List> GetDataChanges(T sourceObj, T newObj) where T : class { var result = new List>(); var type = sourceObj.GetType(); if (!_propertyCaches.ContainsKey(type.FullName)) { _propertyCaches[type.FullName] = getProperties(type); } var properties = _propertyCaches[type.FullName]; foreach (var p in properties) { object[] ignoreCompareChangeAttr = p.PropertyInfo.GetCustomAttributes(typeof(IgnoreCompareChangeAttribute), false); if (ignoreCompareChangeAttr != null && ignoreCompareChangeAttr.Length > 0) { continue; } var sourceValue = p.PropertyInfo.GetValue(sourceObj); var newValue = p.PropertyInfo.GetValue(newObj); var status = false; List> dataChanges = null; if (sourceValue != null && (sourceValue.GetType().GetInterface("IList") != null || sourceValue.GetType().GetInterface("IDictionary") != null)) { var sourceStr = sourceValue == null ? "" : sourceValue.ToJson(); var newStr = newValue == null ? "" : newValue.ToJson(); status = !testValueEqual(sourceStr, newStr, out dataChanges); } else { status = !testValueEqual(sourceValue, newValue, out dataChanges); } if (status) { if (dataChanges != null) { dataChanges.ForEach(O => { var text = ""; if (!Regex.IsMatch(O[0], @"^\[\d+\]")) { text = "."; } var arr = new List() { p.FieldName + text + O[0], p.DisplayName + text + O[1], O[2], O[3] }; if (O.Count > 4) { arr.Add(O[4]); } result.Add(arr); }); } else { var arr = new List() { p.FieldName, p.DisplayName, sourceValue?.ToJson(), newValue?.ToJson(), }; if (p.UseFlag.HasValue) { arr.Add(p.UseFlag.GetValueOrDefault() ? "1" : "0"); } result.Add(arr); } } } return result; } private static bool testValueEqual(dynamic sourceValue, dynamic newValue, out List> dataChanges) { var result = false; dataChanges = null; if ((sourceValue == null && newValue == null) || (sourceValue == null && newValue is string && string.IsNullOrWhiteSpace(newValue)) || (newValue == null && sourceValue is string && string.IsNullOrWhiteSpace(sourceValue))) { result = true; } else if (sourceValue == null || newValue == null) { result = false; } else { var sourceType = sourceValue.GetType(); var newType = newValue.GetType(); if (sourceType.FullName != newType.FullName) { return result; } else if (sourceType.IsValueType && sourceType == typeof(decimal)) { return sourceValue == newValue; } else if (sourceType.IsValueType && sourceType != typeof(bool)) { var diff = sourceValue - newValue; if (diff is TimeSpan ts) { result = ts.Ticks == 0; } else { result = Math.Abs(diff) < TooSmallValue; } } else if (sourceType.GetInterface("IList") != null || sourceType.GetInterface("IDictionary") != null) { //跳过所有类型为数组,集合或字典的字段 result = true; } else if (sourceType.IsClass && sourceType != typeof(string)) { dataChanges = GetDataChanges(sourceValue, newValue); result = false; } else { result = sourceValue == newValue; } } return result; } private static PropertyModel[] getProperties(Type type) { var result = new List(); var allFieldClass = type.GetCustomAttribute(typeof(DataChangeAttribute)) != null; var ps = type.GetProperties(); foreach (var p in ps) { var attr = p.GetCustomAttributes(); bool? useFlag = null; if (!allFieldClass) { if (!attr.Any(O => O is DataChangeAttribute)) { continue; } } else { var tempAttr = attr.Where(O => O is DataChangeAttribute).ToList(); if (tempAttr.Count == 0) { if (attr.Any(O => O is NotMappedAttribute)) { continue; } useFlag = false; } else { useFlag = tempAttr.Any(O => !((DataChangeAttribute)O).IgnoreDiff); if (!useFlag.Value) { continue; } } } var displayName = ""; var displayAttr = (DisplayNameAttribute)attr.Where(O => O is DisplayNameAttribute).FirstOrDefault(); if (displayAttr != null) { displayName = displayAttr.DisplayName; } result.Add(new PropertyModel(p, displayName, useFlag)); } return result.ToArray(); } private class PropertyModel { public string FieldName { get; set; } public string DisplayName { get; set; } public PropertyInfo PropertyInfo { get; set; } public bool? UseFlag { get; set; } public PropertyModel(PropertyInfo propertyInfo, string displayName, bool? useFlag = null) { PropertyInfo = propertyInfo; FieldName = propertyInfo.Name; DisplayName = string.IsNullOrWhiteSpace(displayName) ? PropertyInfo.Name : displayName; UseFlag = useFlag; } } } /// /// 为class标记时:当前类中除[DataChange(true)]和[NotMapped]标记属性外,全部比对; /// 为Property标记时:比对该字段; /// 不支持类型为数组,集合和字典的字段,加了也没用 /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class DataChangeAttribute : Attribute { public bool IgnoreDiff { get; private set; } /// /// 标记该字段比对数据变化时的状态 /// /// /// 若对当前类标记了该标签时,则配置生效,忽略[DataChange(true)]或[NotMapped]标记属性. /// 若未对当前类标记了该标签时,则仅比对[DataChange]标记属性. /// false:比对该字段 /// true:忽略该字段 /// public DataChangeAttribute(bool IgnoreDiff = false) { this.IgnoreDiff = IgnoreDiff; } } }