人生若只如初见

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


关于TNBLOG
TNBLOG,技术分享。技术交流:群号677373950
ICP备案 :渝ICP备18016597号-1
App store Android
精彩评论
{{item.replyName}}
{{item.content}}
{{item.time}}
{{subpj.replyName}}
@{{subpj.beReplyName}}{{subpj.content}}
{{subpj.time}}
猜你喜欢