前言
嘿,大家好!如果你之前看过我分享的文章《C# 7个方法比较两个对象是否相等》,你可能会意识到对象比较在实际业务中经常出现的场景。今天,我想继续与大家分享一个在实际项目中遇到的问题。
有一次,我接手了一个别人的项目,有个新的需求,需要在更新对象信息时比较并记录差异,方便以后跟踪溯源,但我不太想修改底层的实体类,因为这可能会带来不可知的影响,这样会增加很多测试的工作量,而且,我一时也找不到适合的第三方库,因为需求要求同时把实体类的描述特性的内容一起写入数据表。
有没有一种方法,既能满足需求,又能对项目影响最小呢?
最后,我在反射技术上找到了灵感,手动造了一个轮子,封装了一个利用反射来比较对象的方法。这个方法不仅灵活,还能减少对项目的侵入性,非常适合在实际项目中使用,所以分享给大家,一起来看看吧!
封装对象比较方法
创建一个 CommonUtil
静态类,在里面编写封装方法,代码如下,留意注释:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Common.Util
{
/// <summary>
/// 公共工具静态类
/// </summary>
public static class CommonUtil
{
/// <summary>
/// 获取指定类型的属性的 descriptionattribute 特性的值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="PropertyName"></param>
/// <returns></returns>
public static string GetClassDescriptionAttributeValue<T>(T t, string PropertyName)
{
AttributeCollection attributes = TypeDescriptor.GetProperties(t)[PropertyName].Attributes;
DescriptionAttribute descriptionAttribute = (DescriptionAttribute)attributes[typeof(DescriptionAttribute)];
return descriptionAttribute.Description;
}
/// <summary>
/// 比较同一个类型的两个对象的属性的值是否相同,
/// 如果不相同,执行一个自定义的 Action
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="obj1">旧的对象</param>
/// <param name="obj2">新的对象</param>
/// <param name="action">自定义动作</param>
/// <param name="excludePropertys">要忽略比较的字段,默认为空</param>
public static void CompareClassObjectPropertys<T>(T obj1, T obj2, Action<string, string , string, string> action, string[] excludePropertys = null)
{
// 反射获取对象的所有属性
PropertyInfo[] properties = obj1.GetType().GetProperties();
foreach (var po in properties)
{
var propertyName = po.Name;
// 跳过忽略比较的字段
if (excludePropertys != null && excludePropertys.Length > 0)
{
if (excludePropertys.Contains(propertyName)) continue;
}
// 分别获取两个对象的属性的值
var oldValueObj = po.GetValue(obj1, null);
var newValueObj = po.GetValue(obj2, null);
if (oldValueObj == null && newValueObj == null)
{
continue;
}
var oldValue = TranNull<string>(oldValueObj);
var newValue = TranNull<string>(newValueObj);
var propertyDesc = GetClassDescriptionAttributeValue<T>(obj1, propertyName);
if (!oldValue.Equals(newValue))
{
// 如果字段值不相同,执行自定义操作,比如保存到数据库
action(propertyName, propertyDesc, oldValue, newValue);
}
}
}
}
}
使用步骤
创建两个实体类,里面的属性有描述特性,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel.DataAnnotations; using System.ComponentModel; namespace BLL.Model.ViewModels { /// <summary> /// 企业信息表视图模型 /// </summary> public class ComViewModel { [Description("公司码")] public string COMP { get; set; } [Description("公司名称")] public string COMPNAME { get; set; } [Description("纳税人识别号")] public string TAXNO { get; set; } [Description("地址")] public string ADRF1 { get; set; } [Description("邮政编码")] public string PCOD { get; set; } [Description("电话")] public string PHN { get; set; } [Description("递送地址")] public string DELADRF { get; set; } [Description("递送邮政编码")] public string DELPCOD { get; set; } [Description("开户银行")] public string BANK { get; set; } [Description("银行账号")] public string BNKAC { get; set; } [Description("电邮")] public string EMAL { get; set; } [Description("网站")] public string WEB { get; set; } [Description("主要联络人")] public string CONTA { get; set; } [Description("状态")] public string STS { get; set; } [Description("更新用户")] public string USER { get; set; } [Description("更新时间")] public System.DateTime LMDTM { get; set; } } /// <summary> /// HTFP01H 表视图模型 /// </summary> public class ComUpLogViewModel { public int LNID { get; set; } [Description("字段名")] public string FLD { get; set; } [Description("字段描述")] public string DESC { get; set; } [Description("旧值")] public string OLD { get; set; } [Description("新值")] public string ADRF { get; set; } [Description("更新用户")] public string USER { get; set; } [Description("更新时间")] public System.DateTime LMDTM { get; set; } } }
在更新数据时比较对象并记录差异
using Common.Util; /// <summary> /// 更新数据私有方法 /// </summary> /// <param name="oldComViewModel"></param> /// <param name="newComViewModel"></param> /// <returns></returns> private bool Update(ComViewModel oldComViewModel, ComViewModel newComViewModel) { // 1. 比较新旧两个 ViewModel 类的不同,将更改的信息放到一个集合中 var list = new List<ComUpLog>(); var excludeProperys = new string[9] { "USERHT01", "LMDTMHT01" }; CommonUtil.CompareClassObjectPropertys<ComViewModel>( oldComViewModel, newComViewModel, (propertyName, propertyDesc, oldValue, newValue) => { var ComUpLog = new ComUpLog() { FLD = $"[{oldComViewModel.COMP}]{propertyName}", DESC = propertyDesc, OLD = oldValue, ADRF = newValue, USER = newComViewModel.USERHT01, LMDTM = DateTime.Now }; list.Add(ComUpLog); }, excludeProperys); // 2. EF 批量更新数据到数据库 …… }
总结
通过反射技术,我们能够轻松获取对象的属性及其值,从而实现两个对象之间的比较。
这种方式非常灵活,可以达到“非侵入”的效果,对原有项目的影响降到最低。
使用这个封装的方法,你只需根据实际需求稍作修改,就能在项目中更灵活地进行对象比较。
希望这个小技巧能为你的项目带来便利!🌟
最后,如果您有更好的建议和不同的观点,欢迎留言讨论!
往期精彩
- C# 静态类,高手不想让你知道的 15 个真相
- 封装一个 C# 范围判断函数,从此告别重复编写范围判断代码的烦恼
- 用 C# Stopwatch 计时,让代码性能飞起来!
- 轻装上阵,Visual Studio LocalDB:.NET 程序员的本地数据库神器
- 封装一个C#万能基础数据类型转换器,一招解决所有基础类型转换烦恼
- 闲话 .NET(7):.NET Core 能淘汰 .NET FrameWork 吗?
- 常用的 4 种 ORM 框架(EF Core,SqlSugar,FreeSql,Dapper)对比总结
- C# AutoMapper 10个常用方法总结
- C# 7个方法比较两个对象是否相等
- C# 去掉字符串最后一个字符的 4 种方法
我是老杨,一个执着于编程乐趣、至今奋斗在一线的 10年+ 资深研发老鸟,是软件项目管理师,也是快乐的程序猿,持续免费分享全栈实用编程技巧、项目管理经验和职场成长心得。欢迎关注老杨的公众号(名称:代码掌控者),更多干货等你来!