242 lines
9.2 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|