tnblog
首页
视频
资源
登录

C# .Net 字段对比器

91人阅读 2025/5/26 15:40 总访问:310997 评论:0 收藏:0 手机
分类: .NET
  1. 字段对比适用场景 需要知道某人修改了 某项 具体修改的哪个字段,之前是什么值 修改后为什么值 方便知道为什么修改了



  1.  /// <summary>
  2.  /// 字段变更记录器,用于跟踪实体属性的变更并生成操作记录
  3.  /// </summary>
  4.  public class ChangeRecorder
  5.  {
  6.      /// <summary>
  7.      /// 根据两个实体对象的差异生成变更记录
  8.      /// </summary>
  9.      /// <typeparam name="T">实体类型</typeparam>
  10.      /// <param name="oldEntity">旧实体对象</param>
  11.      /// <param name="newEntity">新实体对象</param>
  12.      /// <param name="excludeProperties">要排除的属性名称</param>
  13.      /// <returns>变更记录列表</returns>
  14.      public static List<ChangeRecord> GetChanges<T>(T? oldEntity, T? newEntity, params string[] excludeProperties) where T : class
  15.      {
  16.          var changes = new List<ChangeRecord>(); 
  17.          if (oldEntity == null || newEntity == null)
  18.              return changes;
  19.          // 创建排除属性集合
  20.          var excludeSet = new HashSet<string>(excludeProperties ?? Array.Empty<string>());
  21.          // 获取所有公共属性
  22.          var properties = typeof(T).GetProperties()
  23.              .Where(p => p.CanRead && !excludeSet.Contains(p.Name));
  24.          foreach (var property in properties)
  25.          {
  26.              // 获取属性值
  27.              var oldValue = property.GetValue(oldEntity);
  28.              var newValue = property.GetValue(newEntity);
  29.              // 检查值是否相等
  30.              if (AreValuesEqual(oldValue, newValue))
  31.                  continue;
  32.              // 获取属性的友好名称
  33.              string displayName = GetPropertyDisplayName(property);
  34.              // 创建变更记录
  35.              changes.Add(new ChangeRecord
  36.              {
  37.                  PropertyName = property.Name,
  38.                  DisplayName = displayName,
  39.                  OldValue = oldValue,
  40.                  NewValue = newValue,
  41.                  PropertyType = property.PropertyType
  42.              });
  43.          }
  44.          return changes;
  45.      }
  46.      /// <summary>
  47.      /// 比较两个集合并生成变更记录
  48.      /// </summary>
  49.      /// <typeparam name="T">集合元素类型</typeparam>
  50.      /// <param name="oldCollection">旧集合</param>
  51.      /// <param name="newCollection">新集合</param>
  52.      /// <param name="excludeProperties">要排除的属性名称</param>
  53.      /// <param name="Key">要对比的属性名称</param>
  54.      /// <returns>变更记录列表</returns>
  55.      public static List<CollectionChangeRecord<T>> GetCollectionChanges<T>(IEnumerable<T> oldCollection, IEnumerable<T> newCollection, string Key = "Id", params string[] excludeProperties) where T : class
  56.      {
  57.          var changes = new List<CollectionChangeRecord<T>>();
  58.          if (oldCollection == null && newCollection == null)
  59.              return changes;
  60.          // 确保集合不为null
  61.          oldCollection = oldCollection ?? Enumerable.Empty<T>();
  62.          newCollection = newCollection ?? Enumerable.Empty<T>();
  63.          // 获取集合元素的主键属性
  64.          PropertyInfo idProperty = GetIdProperty<T>(Key);
  65.          if (idProperty == null)
  66.              throw new InvalidOperationException($"无法在类型 {typeof(T).Name} 中找到唯一标识符属性。请确保类型具有名为'Id'的属性或标有[Key]特性的属性。");
  67.          // 转换为字典以便更快地查找
  68.          var oldDict = oldCollection.ToDictionary(item => idProperty.GetValue(item)?.ToString());
  69.          var newDict = newCollection.ToDictionary(item => idProperty.GetValue(item)?.ToString());
  70.          // 查找已删除的项
  71.          foreach (var oldItem in oldCollection)
  72.          {
  73.              string id = idProperty.GetValue(oldItem)?.ToString();
  74.              if (!newDict.ContainsKey(id))
  75.              {
  76.                  changes.Add(new CollectionChangeRecord<T>
  77.                  {
  78.                      ChangeType = CollectionChangeType.Removed,
  79.                      Item = oldItem,
  80.                      ItemId = id
  81.                  });
  82.              }
  83.          }
  84.          // 查找新增的项
  85.          foreach (var newItem in newCollection)
  86.          {
  87.              string id = idProperty.GetValue(newItem)?.ToString();
  88.              if (!oldDict.ContainsKey(id))
  89.              {
  90.                  changes.Add(new CollectionChangeRecord<T>
  91.                  {
  92.                      ChangeType = CollectionChangeType.Added,
  93.                      Item = newItem,
  94.                      ItemId = id
  95.                  });
  96.              }
  97.          }
  98.          // 查找已修改的项
  99.          foreach (var newItem in newCollection)
  100.          {
  101.              string id = idProperty.GetValue(newItem)?.ToString();
  102.              if (oldDict.TryGetValue(id, out T oldItem))
  103.              {
  104.                  var itemChanges = GetChanges(oldItem, newItem, excludeProperties);
  105.                  if (itemChanges.Count > 0)
  106.                  {
  107.                      changes.Add(new CollectionChangeRecord<T>
  108.                      {
  109.                          ChangeType = CollectionChangeType.Modified,
  110.                          Item = newItem,
  111.                          ItemId = id,
  112.                          PropertyChanges = itemChanges
  113.                      });
  114.                  }
  115.              }
  116.          }
  117.          return changes;
  118.      }
  119.      /// <summary>
  120.      /// 获取实体类型的ID属性
  121.      /// </summary>
  122.      /// <typeparam name="T">实体类型</typeparam>
  123.      /// <returns>ID属性信息</returns>
  124.      private static PropertyInfo GetIdProperty<T>(string Key) where T : class
  125.      {
  126.          Type type = typeof(T);
  127.          // 查找名为"Key"的属性
  128.          PropertyInfo idProperty = type.GetProperty(Key);
  129.          if (idProperty != null)
  130.              return idProperty;
  131.          // 查找标有[Key]特性的属性
  132.          foreach (var property in type.GetProperties())
  133.          {
  134.              var keyAttribute = property.GetCustomAttribute(typeof(System.ComponentModel.DataAnnotations.KeyAttribute));
  135.              if (keyAttribute != null)
  136.                  return property;
  137.          }
  138.          // 如果找不到合适的ID属性,返回null
  139.          return null;
  140.      }
  141.      /// <summary>
  142.      /// 生成格式化的变更记录消息
  143.      /// </summary>
  144.      /// <param name="changes">变更记录列表</param>
  145.      /// <param name="entityDisplayName">实体显示名称</param>
  146.      /// <param name="tips">实体显示名称</param>
  147.      /// <param name="DateformatValue">实体显示名称</param>
  148.      /// <returns>格式化的变更记录</returns>
  149.      public static List<string> FormatChanges(List<ChangeRecord> changes, string entityDisplayName = null, string tips = null, string DateformatValue = "yyyy-MM-dd HH:mm:ss")
  150.      {
  151.          var messages = new List<string>();
  152.          foreach (var change in changes)
  153.          {
  154.              string oldValueStr = FormatValue(change.OldValue, DateformatValue);
  155.              string newValueStr = FormatValue(change.NewValue, DateformatValue);
  156.              string message;
  157.              if (!string.IsNullOrEmpty(entityDisplayName))
  158.              {
  159.                  message = $"{entityDisplayName} 将 {change.DisplayName} 从 {oldValueStr} 改为 {newValueStr}";
  160.              }
  161.              else
  162.              {
  163.                  message = $"{change.DisplayName} 将 {oldValueStr} 改为 {newValueStr}";
  164.              }
  165.              if (!string.IsNullOrEmpty(tips))
  166.              {
  167.                  message += tips;
  168.              }
  169.              messages.Add(message);
  170.          }
  171.          return messages;
  172.      }
  173.      /// <summary>
  174.      /// 格式化集合变更记录消息
  175.      /// </summary>
  176.      /// <typeparam name="T">集合元素类型</typeparam>
  177.      /// <param name="collectionChanges">集合变更记录</param>
  178.      /// <param name="collectionDisplayName">集合显示名称</param>
  179.      /// <param name="entityDisplayProperty">实体显示属性名称</param>
  180.      /// <param name="tips">实体显示属性名称</param>
  181.      /// <param name="DateformatValue">实体显示名称</param>
  182.      /// <returns>格式化的集合变更记录</returns>
  183.      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
  184.      {
  185.          var messages = new List<string>();
  186.          PropertyInfo displayProperty = null;
  187.          if (!string.IsNullOrEmpty(entityDisplayProperty))
  188.              displayProperty = typeof(T).GetProperty(entityDisplayProperty);
  189.          foreach (var change in collectionChanges)
  190.          {
  191.              string itemDisplay = GetItemDisplayName(change.Item, displayProperty);
  192.              switch (change.ChangeType)
  193.              {
  194.                  case CollectionChangeType.Added:
  195.                      messages.Add($"{collectionDisplayName} 新增 {tips}:{itemDisplay}");
  196.                      break;
  197.                  case CollectionChangeType.Removed:
  198.                      messages.Add($"{collectionDisplayName} 移除 {tips}:{itemDisplay}");
  199.                      break;
  200.                  case CollectionChangeType.Modified:
  201.                      messages.Add($"{collectionDisplayName} 修改 {tips}:{itemDisplay}");
  202.                      foreach (var propertyChange in change.PropertyChanges)
  203.                      {
  204.                          string oldValueStr = FormatValue(propertyChange.OldValue, DateformatValue);
  205.                          string newValueStr = FormatValue(propertyChange.NewValue, DateformatValue);
  206.                          messages.Add($"  - {propertyChange.DisplayName}:从 {oldValueStr} 改为 {newValueStr}");
  207.                      }
  208.                      break;
  209.              }
  210.          }
  211.          return messages;
  212.      }
  213.      /// <summary>
  214.      /// 获取实体的显示名称
  215.      /// </summary>
  216.      private static string GetItemDisplayName<T>(T item, PropertyInfo displayProperty) where T : class
  217.      {
  218.          if (item == null)
  219.              return "未知";
  220.          if (displayProperty != null)
  221.          {
  222.              var displayValue = displayProperty.GetValue(item);
  223.              return displayValue?.ToString() ?? "未知";
  224.          }
  225.          return item.ToString();
  226.      }
  227.      /// <summary>
  228.      /// 检查两个值是否相等
  229.      /// </summary>
  230.      private static bool AreValuesEqual(object value1, object value2)
  231.      {
  232.          if (value1 == null && value2 == null)
  233.              return true;
  234.          if (value1 == null || value2 == null)
  235.              return false;
  236.          // 处理特殊类型的比较
  237.          if (value1 is DateTime date1 && value2 is DateTime date2)
  238.          {
  239.              // 比较日期,忽略毫秒
  240.              return date1.Year == date2.Year &&
  241.                     date1.Month == date2.Month &&
  242.                     date1.Day == date2.Day &&
  243.                     date1.Hour == date2.Hour &&
  244.                     date1.Minute == date2.Minute &&
  245.                     date1.Second == date2.Second;
  246.          }
  247.          // 处理 List<T> / IEnumerable<T>
  248.          if (value1 is IEnumerable enum1 && value2 is IEnumerable enum2)
  249.          {
  250.              var list1 = enum1.Cast<object>().ToList();
  251.              var list2 = enum2.Cast<object>().ToList();
  252.              if (list1.Count != list2.Count)
  253.                  return false;
  254.              for (int i = 0; i < list1.Count; i++)
  255.              {
  256.                  if (!AreValuesEqual(list1[i], list2[i]))
  257.                      return false;
  258.              }
  259.              return true;
  260.          }
  261.          // 默认比较
  262.          return value1.Equals(value2);
  263.      }
  264.      /// <summary>
  265.      /// 格式化属性值为可读字符串
  266.      /// </summary>
  267.      private static string FormatValue(object value, string DateformatValue)
  268.      {
  269.          if (value == null)
  270.              return "没有";
  271.          // 处理特定类型
  272.          if (value is DateTime dateTime)
  273.              try
  274.              {
  275.                  return dateTime.ToString(DateformatValue ?? "yyyy-MM-dd HH:mm:ss");
  276.              }
  277.              catch (Exception)
  278.              {
  279.                  return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
  280.              }
  281.          if (value is bool boolValue)
  282.              return boolValue ? "是" : "否";
  283.          if (value is Enum)
  284.              return GetEnumDisplayName(value);
  285.          return value.ToString();
  286.      }
  287.      /// <summary>
  288.      /// 获取枚举的显示名称
  289.      /// </summary>
  290.      private static string GetEnumDisplayName(object enumValue)
  291.      {
  292.          Type enumType = enumValue.GetType();
  293.          string name = Enum.GetName(enumType, enumValue);
  294.          if (name == null)
  295.              return enumValue.ToString();
  296.          // 查找是否有显示名称特性
  297.          MemberInfo[] memberInfo = enumType.GetMember(name);
  298.          if (memberInfo.Length > 0)
  299.          {
  300.              var displayAttr = memberInfo[0].GetCustomAttribute<DisplayNameAttribute>();
  301.              if (displayAttr != null)
  302.                  return displayAttr.DisplayName;
  303.              var descAttr = memberInfo[0].GetCustomAttribute<DescriptionAttribute>();
  304.              if (descAttr != null)
  305.                  return descAttr.Description;
  306.          }
  307.          return name;
  308.      }
  309.      /// <summary>
  310.      /// 获取属性的显示名称
  311.      /// </summary>
  312.      private static string GetPropertyDisplayName(PropertyInfo property)
  313.      {
  314.          // 1. 尝试获取 DisplayName 特性
  315.          var displayAttr = property.GetCustomAttribute<DisplayNameAttribute>();
  316.          if (displayAttr != null && !string.IsNullOrEmpty(displayAttr.DisplayName))
  317.              return displayAttr.DisplayName;
  318.          // 2. 尝试获取 Description 特性
  319.          var descAttr = property.GetCustomAttribute<DescriptionAttribute>();
  320.          if (descAttr != null && !string.IsNullOrEmpty(descAttr.Description))
  321.              return descAttr.Description;
  322.          // 3. 尝试获取 XmlComment 特性(如果存在)
  323.          var xmlCommentAttr = property.GetCustomAttribute<XmlCommentAttribute>();
  324.          if (xmlCommentAttr != null && !string.IsNullOrEmpty(xmlCommentAttr.Comment))
  325.              return xmlCommentAttr.Comment;
  326.          // 4. 默认返回属性名
  327.          return property.Name;
  328.      }
  329.  }
  330.  /// <summary>
  331.  /// 变更记录实体类
  332.  /// </summary>
  333.  public class ChangeRecord
  334.  {
  335.      /// <summary>
  336.      /// 属性名
  337.      /// </summary>
  338.      public string PropertyName { get; set; }
  339.      /// <summary>
  340.      /// 显示名称
  341.      /// </summary>
  342.      public string DisplayName { get; set; }
  343.      /// <summary>
  344.      /// 旧值
  345.      /// </summary>
  346.      public object OldValue { get; set; }
  347.      /// <summary>
  348.      /// 新值
  349.      /// </summary>
  350.      public object NewValue { get; set; }
  351.      /// <summary>
  352.      /// 属性类型
  353.      /// </summary>
  354.      public Type PropertyType { get; set; }
  355.  }
  356.  /// <summary>
  357.  /// 集合变更类型枚举
  358.  /// </summary>
  359.  public enum CollectionChangeType
  360.  {
  361.      /// <summary>
  362.      /// 新增
  363.      /// </summary>
  364.      Added,
  365.      /// <summary>
  366.      /// 删除
  367.      /// </summary>
  368.      Removed,
  369.      /// <summary>
  370.      /// 修改
  371.      /// </summary>
  372.      Modified
  373.  }
  374.  /// <summary>
  375.  /// 集合变更记录实体类
  376.  /// </summary>
  377.  public class CollectionChangeRecord<T> where T : class
  378.  {
  379.      /// <summary>
  380.      /// 变更类型
  381.      /// </summary>
  382.      public CollectionChangeType ChangeType { get; set; }
  383.      /// <summary>
  384.      /// 项目ID
  385.      /// </summary>
  386.      public string ItemId { get; set; }
  387.      /// <summary>
  388.      /// 项目实例
  389.      /// </summary>
  390.      public T Item { get; set; }
  391.      /// <summary>
  392.      /// 属性变更列表(仅在修改类型时有值)
  393.      /// </summary>
  394.      public List<ChangeRecord> PropertyChanges { get; set; } = new List<ChangeRecord>();
  395.  }
  396.  /// <summary>
  397.  /// XML注释特性,用于在运行时提供XML注释
  398.  /// </summary>
  399.  [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class | AttributeTargets.Enum)]
  400.  public class XmlCommentAttribute : Attribute
  401.  {
  402.      /// <summary>
  403.      /// 注释内容
  404.      /// </summary>
  405.      public string Comment { get; }
  406.      /// <summary>
  407.      /// 创建XML注释特性实例
  408.      /// </summary>
  409.      /// <param name="comment">注释内容</param>
  410.      public XmlCommentAttribute(string comment)
  411.      {
  412.          Comment = comment;
  413.      }
  414.  }
  415.  #region 使用示例
  416.  // 使用示例:
  417.  // 比较两个集合
  418.  // var changes = ChangeRecorder.GetCollectionChanges(oldTaskPersonRelation.Where(t => t.RelationType == 1).ToList(), TaskPersonRelationDto.Where(t => t.RelationType == 1).ToList(),"LastModificationTime", "LastModifierId", "ConcurrencyStamp");
  419.  // 
  420.  // // 格式化集合变更记录
  421.  // var messages = ChangeRecorder.FormatCollectionChanges(changes, "项目成员", "PersonName");
  422.  #endregion


评价

net core 使用 EF Code First

下面这些内容很老了看这篇:https://www.tnblog.net/aojiancc2/article/details/5365 项目使用多层,把数据库访问...

cAPS.net 保存base64位格式的图片

publicvoidUpload() { //取出图片对应的base64位字符 stringimgBase=Request[&quot;imgBase&quot;]; //c#里边的base6...

Quartz.net实例动态改变周期调度。misfire、Cron

Quartz:Java编写的开源的任务调度作业框架 类似Timer之类定时执行的功能,但是更强大Quartz.NET:是把Quartz转成C# NuGet...

.net Windows服务发布、安装、卸载、监听脚本。服务调试

一、脚本 为方便不用每次都去写安装卸载的脚本1.安装脚本@echooff @echo开始安装【服务】 %SystemRoot%\Microsoft.NET\Fr...

c、VB.net中全角半角转换方法

///&lt;summary&gt; ///转全角的函数(SBCcase) ///&lt;/summary&gt; ///&lt;paramname=&quot;input&quot;&gt;任意字符串...

.net mvc分部页,.net core分部页

.net分部页的三种方式第一种:@Html.Partial(&quot;_分部页&quot;)第二种:@{ Html.RenderPartial(&quot;分部页&quot;);}...

.net实现QQ邮箱发送邮件功能

1、微软已经帮我们封装好了发送邮件的类MailMessage,MailMessage类构造一些邮件信息,然后通过SmtpClient进行邮件发送。Mai...

StackExchange.Redis操作redis(net core支持)

官方git开源地址https://github.com/StackExchange/StackExchange.Redis官方文档在docs里边都是官方的文档通过nuget命令下...

windows 自带的netsh进行端口映射

使用netsh 把本地任意ip的25566端口 映射到192.168.81.234的25565端口netshinterfaceportproxyaddv4tov4listenaddress=0.0....

确保.net程序始终以管理员身份运行

usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Text; usingSystem.Threading.Tasks; ...

ASP.net Timer细节处理

Timer的用法:1:本人称之为计时器,是asp.net官方的一种。用法即是计时所用 2:关于计时有很多中方式,本人学识有限,暂...

.net core 使用session

tip:net core 2.2后可以直接启用session了,不用在自己添加一次session依赖,本身就添加了使用nuget添加引用Microsoft.AspN...

通俗易懂,什么是.net?什么是.net Framework?什么是.net core?

朋友圈@蓝羽 看到一篇文章写的太详细太通俗了,搬过来细细看完,保证你对.NET有个新的认识理解原文地址:https://www.cnblo...

asp.net core2.0 依赖注入 AddTransient与AddScoped的区别

asp.net core主要提供了三种依赖注入的方式其中AddTransient与AddSingleton比较好区别AddTransient瞬时模式:每次都获取一...

asp.net主动推送百度seo

虽然可以使用百度提供的js自动推送,但是估计度娘还是希望主动推送一点。哈哈^_^,女孩子嘛大多都喜欢被动一点。publicclass...
若人生只如初见,愿还是相遇的那个下午,阳光明媚转身一笑便如春暖花开
排名
16
文章
54
粉丝
7
评论
13
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术
Fightだよじゃねつの!