
- 字段对比适用场景 需要知道某人修改了 某项 具体修改的哪个字段,之前是什么值 修改后为什么值 方便知道为什么修改了
- /// <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
评价
排名
16
文章
54
粉丝
7
评论
13
点击input弹出table数据表格
剑轩 : 点赞!~
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:
50010702506256


欢迎加群交流技术