C# .Net 字段对比器
电脑版发表于:2025/5/26 15:40
字段对比适用场景 需要知道某人修改了 某项 具体修改的哪个字段,之前是什么值 修改后为什么值 方便知道为什么修改了
/// <summary> /// 字段变更记录器,用于跟踪实体属性的变更并生成操作记录 /// </summary> public class ChangeRecorder { /// <summary> /// 根据两个实体对象的差异生成变更记录 /// </summary> /// <typeparam name="T">实体类型</typeparam> /// <param name="oldEntity">旧实体对象</param> /// <param name="newEntity">新实体对象</param> /// <param name="excludeProperties">要排除的属性名称</param> /// <returns>变更记录列表</returns> public static List<ChangeRecord> GetChanges<T>(T? oldEntity, T? newEntity, params string[] excludeProperties) where T : class { var changes = new List<ChangeRecord>(); if (oldEntity == null || newEntity == null) return changes; // 创建排除属性集合 var excludeSet = new HashSet<string>(excludeProperties ?? Array.Empty<string>()); // 获取所有公共属性 var properties = typeof(T).GetProperties() .Where(p => p.CanRead && !excludeSet.Contains(p.Name)); foreach (var property in properties) { // 获取属性值 var oldValue = property.GetValue(oldEntity); var newValue = property.GetValue(newEntity); // 检查值是否相等 if (AreValuesEqual(oldValue, newValue)) continue; // 获取属性的友好名称 string displayName = GetPropertyDisplayName(property); // 创建变更记录 changes.Add(new ChangeRecord { PropertyName = property.Name, DisplayName = displayName, OldValue = oldValue, NewValue = newValue, PropertyType = property.PropertyType }); } return changes; } /// <summary> /// 比较两个集合并生成变更记录 /// </summary> /// <typeparam name="T">集合元素类型</typeparam> /// <param name="oldCollection">旧集合</param> /// <param name="newCollection">新集合</param> /// <param name="excludeProperties">要排除的属性名称</param> /// <param name="Key">要对比的属性名称</param> /// <returns>变更记录列表</returns> public static List<CollectionChangeRecord<T>> GetCollectionChanges<T>(IEnumerable<T> oldCollection, IEnumerable<T> newCollection, string Key = "Id", params string[] excludeProperties) where T : class { var changes = new List<CollectionChangeRecord<T>>(); if (oldCollection == null && newCollection == null) return changes; // 确保集合不为null oldCollection = oldCollection ?? Enumerable.Empty<T>(); newCollection = newCollection ?? Enumerable.Empty<T>(); // 获取集合元素的主键属性 PropertyInfo idProperty = GetIdProperty<T>(Key); if (idProperty == null) throw new InvalidOperationException($"无法在类型 {typeof(T).Name} 中找到唯一标识符属性。请确保类型具有名为'Id'的属性或标有[Key]特性的属性。"); // 转换为字典以便更快地查找 var oldDict = oldCollection.ToDictionary(item => idProperty.GetValue(item)?.ToString()); var newDict = newCollection.ToDictionary(item => idProperty.GetValue(item)?.ToString()); // 查找已删除的项 foreach (var oldItem in oldCollection) { string id = idProperty.GetValue(oldItem)?.ToString(); if (!newDict.ContainsKey(id)) { changes.Add(new CollectionChangeRecord<T> { ChangeType = CollectionChangeType.Removed, Item = oldItem, ItemId = id }); } } // 查找新增的项 foreach (var newItem in newCollection) { string id = idProperty.GetValue(newItem)?.ToString(); if (!oldDict.ContainsKey(id)) { changes.Add(new CollectionChangeRecord<T> { ChangeType = CollectionChangeType.Added, Item = newItem, ItemId = id }); } } // 查找已修改的项 foreach (var newItem in newCollection) { string id = idProperty.GetValue(newItem)?.ToString(); if (oldDict.TryGetValue(id, out T oldItem)) { var itemChanges = GetChanges(oldItem, newItem, excludeProperties); if (itemChanges.Count > 0) { changes.Add(new CollectionChangeRecord<T> { ChangeType = CollectionChangeType.Modified, Item = newItem, ItemId = id, PropertyChanges = itemChanges }); } } } return changes; } /// <summary> /// 获取实体类型的ID属性 /// </summary> /// <typeparam name="T">实体类型</typeparam> /// <returns>ID属性信息</returns> private static PropertyInfo GetIdProperty<T>(string Key) where T : class { Type type = typeof(T); // 查找名为"Key"的属性 PropertyInfo idProperty = type.GetProperty(Key); if (idProperty != null) return idProperty; // 查找标有[Key]特性的属性 foreach (var property in type.GetProperties()) { var keyAttribute = property.GetCustomAttribute(typeof(System.ComponentModel.DataAnnotations.KeyAttribute)); if (keyAttribute != null) return property; } // 如果找不到合适的ID属性,返回null return null; } /// <summary> /// 生成格式化的变更记录消息 /// </summary> /// <param name="changes">变更记录列表</param> /// <param name="entityDisplayName">实体显示名称</param> /// <param name="tips">实体显示名称</param> /// <param name="DateformatValue">实体显示名称</param> /// <returns>格式化的变更记录</returns> public static List<string> FormatChanges(List<ChangeRecord> changes, string entityDisplayName = null, string tips = null, string DateformatValue = "yyyy-MM-dd HH:mm:ss") { var messages = new List<string>(); foreach (var change in changes) { string oldValueStr = FormatValue(change.OldValue, DateformatValue); string newValueStr = FormatValue(change.NewValue, DateformatValue); string message; if (!string.IsNullOrEmpty(entityDisplayName)) { message = $"{entityDisplayName} 将 {change.DisplayName} 从 {oldValueStr} 改为 {newValueStr}"; } else { message = $"{change.DisplayName} 将 {oldValueStr} 改为 {newValueStr}"; } if (!string.IsNullOrEmpty(tips)) { message += tips; } messages.Add(message); } return messages; } /// <summary> /// 格式化集合变更记录消息 /// </summary> /// <typeparam name="T">集合元素类型</typeparam> /// <param name="collectionChanges">集合变更记录</param> /// <param name="collectionDisplayName">集合显示名称</param> /// <param name="entityDisplayProperty">实体显示属性名称</param> /// <param name="tips">实体显示属性名称</param> /// <param name="DateformatValue">实体显示名称</param> /// <returns>格式化的集合变更记录</returns> public static List<string> FormatCollectionChanges<T>(List<CollectionChangeRecord<T>> collectionChanges, string collectionDisplayName, string entityDisplayProperty = null, string tips = "", string DateformatValue = "yyyy-MM-dd HH:mm:ss") where T : class { var messages = new List<string>(); PropertyInfo displayProperty = null; if (!string.IsNullOrEmpty(entityDisplayProperty)) displayProperty = typeof(T).GetProperty(entityDisplayProperty); foreach (var change in collectionChanges) { string itemDisplay = GetItemDisplayName(change.Item, displayProperty); switch (change.ChangeType) { case CollectionChangeType.Added: messages.Add($"{collectionDisplayName} 新增 {tips}:{itemDisplay}"); break; case CollectionChangeType.Removed: messages.Add($"{collectionDisplayName} 移除 {tips}:{itemDisplay}"); break; case CollectionChangeType.Modified: messages.Add($"{collectionDisplayName} 修改 {tips}:{itemDisplay}"); foreach (var propertyChange in change.PropertyChanges) { string oldValueStr = FormatValue(propertyChange.OldValue, DateformatValue); string newValueStr = FormatValue(propertyChange.NewValue, DateformatValue); messages.Add($" - {propertyChange.DisplayName}:从 {oldValueStr} 改为 {newValueStr}"); } break; } } return messages; } /// <summary> /// 获取实体的显示名称 /// </summary> private static string GetItemDisplayName<T>(T item, PropertyInfo displayProperty) where T : class { if (item == null) return "未知"; if (displayProperty != null) { var displayValue = displayProperty.GetValue(item); return displayValue?.ToString() ?? "未知"; } return item.ToString(); } /// <summary> /// 检查两个值是否相等 /// </summary> private static bool AreValuesEqual(object value1, object value2) { if (value1 == null && value2 == null) return true; if (value1 == null || value2 == null) return false; // 处理特殊类型的比较 if (value1 is DateTime date1 && value2 is DateTime date2) { // 比较日期,忽略毫秒 return date1.Year == date2.Year && date1.Month == date2.Month && date1.Day == date2.Day && date1.Hour == date2.Hour && date1.Minute == date2.Minute && date1.Second == date2.Second; } // 处理 List<T> / IEnumerable<T> if (value1 is IEnumerable enum1 && value2 is IEnumerable enum2) { var list1 = enum1.Cast<object>().ToList(); var list2 = enum2.Cast<object>().ToList(); if (list1.Count != list2.Count) return false; for (int i = 0; i < list1.Count; i++) { if (!AreValuesEqual(list1[i], list2[i])) return false; } return true; } // 默认比较 return value1.Equals(value2); } /// <summary> /// 格式化属性值为可读字符串 /// </summary> private static string FormatValue(object value, string DateformatValue) { if (value == null) return "没有"; // 处理特定类型 if (value is DateTime dateTime) try { return dateTime.ToString(DateformatValue ?? "yyyy-MM-dd HH:mm:ss"); } catch (Exception) { return dateTime.ToString("yyyy-MM-dd HH:mm:ss"); } if (value is bool boolValue) return boolValue ? "是" : "否"; if (value is Enum) return GetEnumDisplayName(value); return value.ToString(); } /// <summary> /// 获取枚举的显示名称 /// </summary> private static string GetEnumDisplayName(object enumValue) { Type enumType = enumValue.GetType(); string name = Enum.GetName(enumType, enumValue); if (name == null) return enumValue.ToString(); // 查找是否有显示名称特性 MemberInfo[] memberInfo = enumType.GetMember(name); if (memberInfo.Length > 0) { var displayAttr = memberInfo[0].GetCustomAttribute<DisplayNameAttribute>(); if (displayAttr != null) return displayAttr.DisplayName; var descAttr = memberInfo[0].GetCustomAttribute<DescriptionAttribute>(); if (descAttr != null) return descAttr.Description; } return name; } /// <summary> /// 获取属性的显示名称 /// </summary> private static string GetPropertyDisplayName(PropertyInfo property) { // 1. 尝试获取 DisplayName 特性 var displayAttr = property.GetCustomAttribute<DisplayNameAttribute>(); if (displayAttr != null && !string.IsNullOrEmpty(displayAttr.DisplayName)) return displayAttr.DisplayName; // 2. 尝试获取 Description 特性 var descAttr = property.GetCustomAttribute<DescriptionAttribute>(); if (descAttr != null && !string.IsNullOrEmpty(descAttr.Description)) return descAttr.Description; // 3. 尝试获取 XmlComment 特性(如果存在) var xmlCommentAttr = property.GetCustomAttribute<XmlCommentAttribute>(); if (xmlCommentAttr != null && !string.IsNullOrEmpty(xmlCommentAttr.Comment)) return xmlCommentAttr.Comment; // 4. 默认返回属性名 return property.Name; } } /// <summary> /// 变更记录实体类 /// </summary> public class ChangeRecord { /// <summary> /// 属性名 /// </summary> public string PropertyName { get; set; } /// <summary> /// 显示名称 /// </summary> public string DisplayName { get; set; } /// <summary> /// 旧值 /// </summary> public object OldValue { get; set; } /// <summary> /// 新值 /// </summary> public object NewValue { get; set; } /// <summary> /// 属性类型 /// </summary> public Type PropertyType { get; set; } } /// <summary> /// 集合变更类型枚举 /// </summary> public enum CollectionChangeType { /// <summary> /// 新增 /// </summary> Added, /// <summary> /// 删除 /// </summary> Removed, /// <summary> /// 修改 /// </summary> Modified } /// <summary> /// 集合变更记录实体类 /// </summary> public class CollectionChangeRecord<T> where T : class { /// <summary> /// 变更类型 /// </summary> public CollectionChangeType ChangeType { get; set; } /// <summary> /// 项目ID /// </summary> public string ItemId { get; set; } /// <summary> /// 项目实例 /// </summary> public T Item { get; set; } /// <summary> /// 属性变更列表(仅在修改类型时有值) /// </summary> public List<ChangeRecord> PropertyChanges { get; set; } = new List<ChangeRecord>(); } /// <summary> /// XML注释特性,用于在运行时提供XML注释 /// </summary> [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Enum)] public class XmlCommentAttribute : Attribute { /// <summary> /// 注释内容 /// </summary> public string Comment { get; } /// <summary> /// 创建XML注释特性实例 /// </summary> /// <param name="comment">注释内容</param> public XmlCommentAttribute(string comment) { Comment = comment; } } #region 使用示例 // 使用示例: // 比较两个集合 // var changes = ChangeRecorder.GetCollectionChanges(oldTaskPersonRelation.Where(t => t.RelationType == 1).ToList(), TaskPersonRelationDto.Where(t => t.RelationType == 1).ToList(),"LastModificationTime", "LastModifierId", "ConcurrencyStamp"); // // // 格式化集合变更记录 // var messages = ChangeRecorder.FormatCollectionChanges(changes, "项目成员", "PersonName"); #endregion