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