Files
zszq-trs/Framework/YLErp.Core/Helpers/DataChangeHelper.cs
T
2024-05-09 14:06:26 +08:00

242 lines
9.2 KiB
C#

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<string, PropertyModel[]> _propertyCaches;
static DataChangeHelper()
{
_propertyCaches = new Dictionary<string, PropertyModel[]>();
}
const double TooSmallValue = 1e-5;
public static List<List<string>> GetDataChanges<T>(T sourceObj, T newObj) where T : class
{
var result = new List<List<string>>();
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<List<string>> 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<string>() {
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<string>() {
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<List<string>> 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<PropertyModel>();
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;
}
}
}
/// <summary>
/// <para>为class标记时:当前类中除[DataChange(true)]和[NotMapped]标记属性外,全部比对;</para>
/// <para>为Property标记时:比对该字段;</para>
/// <para>不支持类型为数组,集合和字典的字段,加了也没用</para>
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class DataChangeAttribute : Attribute
{
public bool IgnoreDiff { get; private set; }
/// <summary>
/// 标记该字段比对数据变化时的状态
/// </summary>
/// <param name="IgnoreDiff">
/// <para>若对当前类标记了该标签时,则配置生效,忽略[DataChange(true)]或[NotMapped]标记属性.</para>
/// <para>若未对当前类标记了该标签时,则仅比对[DataChange]标记属性.</para>
/// <para>false:比对该字段</para>
/// <para>true:忽略该字段</para>
/// </param>
public DataChangeAttribute(bool IgnoreDiff = false)
{
this.IgnoreDiff = IgnoreDiff;
}
}
}