如何获取 C# 类中发生数据变化的属性信息
<h3>一、前言</h3><p>在平时的开发中,当用户修改数据时,一直没有很好的办法来记录具体修改了那些信息,只能暂时采用将类序列化成 json 字符串,然后全塞入到日志中的方式,此时如果我们想要知道用户具体改变了哪几个字段的值的话就很困难了。因此,趁着这个假期,就来解决这个一直遗留的小问题,本篇文章记录了我目前实现的方法,如果你有不同于文中所列出的方案的话,欢迎指出。</p>
<p>代码仓储地址:https://github.com/Lanesra712/ingos-common/tree/master/sample/csharp/get-data-changed-properties</p>
<p> </p>
<h3>二、Step by Step</h3>
<h4>1、需求场景</h4>
<p>一个经常遇到的使用场景,用户 A 修改了某个表单页面上的数据信息,然后提交到我们的服务端完成数据的更新,对于具有某些权限的用户来说,则是期望可以看到所有用户对于该表单进行操作前后的数据变更。</p>
<h4>2、解决方法</h4>
<p>既然想要得知用户操作前后的数据差异,我们肯定需要去对用户操作前后的数据进行比对,这里就落到我们承接数据的类身上。</p>
<p>在我们定义类中的属性时,更多的是使用自动属性的方式来完成属性的 getter、setter 声明,而完整的属性声明方式则需要我们定义一个字段用来承接对于该属性的变更。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 自动属性声明</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Entity1
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> Guid Id { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 完整的属性声明</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Entity2
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Guid _id;
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Guid Id
{
</span><span style="color: rgba(0, 0, 255, 1)">get</span> =><span style="color: rgba(0, 0, 0, 1)"> _id;
</span><span style="color: rgba(0, 0, 255, 1)">set</span> => _id =<span style="color: rgba(0, 0, 0, 1)"> value;
}
}</span></pre>
</div>
<p>因为在给属性进行赋值的时候,需要调用属性的 set 构造器,因此,在 set 构造器内部我们是不是就可以直接对新赋的值进行判断,从而记录下属性的变更过程,改造后的类属性声明代码如下。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Sample
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> _a;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> A
{
</span><span style="color: rgba(0, 0, 255, 1)">get</span> =><span style="color: rgba(0, 0, 0, 1)"> _a;
</span><span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (_a ==<span style="color: rgba(0, 0, 0, 1)"> value)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">string</span> old =<span style="color: rgba(0, 0, 0, 1)"> _a;
_a </span>=<span style="color: rgba(0, 0, 0, 1)"> value;
propertyChangelogs.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> PropertyChangelog<Sample><span style="color: rgba(0, 0, 0, 1)">(nameof(A), old, _a));
}
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">double</span><span style="color: rgba(0, 0, 0, 1)"> _b;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">double</span><span style="color: rgba(0, 0, 0, 1)"> B
{
</span><span style="color: rgba(0, 0, 255, 1)">get</span> =><span style="color: rgba(0, 0, 0, 1)"> _b;
</span><span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (_b ==<span style="color: rgba(0, 0, 0, 1)"> value)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">double</span> old =<span style="color: rgba(0, 0, 0, 1)"> _b;
_b </span>=<span style="color: rgba(0, 0, 0, 1)"> value;
propertyChangelogs.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> PropertyChangelog<Sample><span style="color: rgba(0, 0, 0, 1)">(nameof(B), old.ToString(), _b.ToString()));
}
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> IList<PropertyChangelog<Sample>> propertyChangelogs = <span style="color: rgba(0, 0, 255, 1)">new</span> List<PropertyChangelog<Sample>><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">public</span> IEnumerable<PropertyChangelog<Sample>> Changelogs() =><span style="color: rgba(0, 0, 0, 1)"> propertyChangelogs;
}</span></pre>
</div>
<p>在改造后的类属性声明中,我们在属性的 set 构造器中将新赋的值与原先的值进行判断,当存在两次值不一样时,就写入到变更记录的集合中,从而实现记录数据变更的目的。这里对于变更记录的实体类属性定义如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> PropertyChangelog<T><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> ctor
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> PropertyChangelog()
{ }
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> ctor
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="propertyName"></span><span style="color: rgba(0, 128, 0, 1)">属性名称</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="oldValue"></span><span style="color: rgba(0, 128, 0, 1)">旧值</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="newValue"></span><span style="color: rgba(0, 128, 0, 1)">新值</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> PropertyChangelog(<span style="color: rgba(0, 0, 255, 1)">string</span> propertyName, <span style="color: rgba(0, 0, 255, 1)">string</span> oldValue, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> newValue)
{
PropertyName </span>=<span style="color: rgba(0, 0, 0, 1)"> propertyName;
OldValue </span>=<span style="color: rgba(0, 0, 0, 1)"> oldValue;
NewValue </span>=<span style="color: rgba(0, 0, 0, 1)"> newValue;
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> ctor
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="className"></span><span style="color: rgba(0, 128, 0, 1)">类名</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="propertyName"></span><span style="color: rgba(0, 128, 0, 1)">属性名称</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="oldValue"></span><span style="color: rgba(0, 128, 0, 1)">旧值</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="newValue"></span><span style="color: rgba(0, 128, 0, 1)">新值</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="changedTime"></span><span style="color: rgba(0, 128, 0, 1)">修改时间</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> PropertyChangelog(<span style="color: rgba(0, 0, 255, 1)">string</span> className, <span style="color: rgba(0, 0, 255, 1)">string</span> propertyName, <span style="color: rgba(0, 0, 255, 1)">string</span> oldValue, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> newValue, DateTime changedTime)
: </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">(propertyName, oldValue, newValue)
{
ClassName </span>=<span style="color: rgba(0, 0, 0, 1)"> className;
ChangedTime </span>=<span style="color: rgba(0, 0, 0, 1)"> changedTime;
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 类名称
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> ClassName { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(T).FullName;
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 属性名称
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> PropertyName { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 旧值
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> OldValue { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 新值
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> NewValue { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 修改时间
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> DateTime ChangedTime { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } =<span style="color: rgba(0, 0, 0, 1)"> DateTime.Now;
}</span></pre>
</div>
<p><img src="https://img2018.cnblogs.com/common/1310859/202002/1310859-20200207153407667-83374618.gif" alt=""></p>
<p>可以看到,在我们对 Sample 类进行初始化赋值时,记录了两次关于类属性的数据变更记录,而当我们进行重新赋值时,只有属性 A 发生了数据改变,因此只记录了属性 A 的数据变更记录。</p>
<p>虽然这里已经达到我们的目的,但是如果采用这种方式的话,相当于原先项目中需要实现数据记录功能的类的属性声明方式全部需要重写,同时,基于 C# 本身已经提供了自动属性的方式来简化属性声明,结果现在我们又回到了传统属性的声明方式,似乎显得有些不太聪明的样子。因此,既然通过一个个属性进行比较的方式过于繁琐,这里我们通过反射的方式直接对比修改前后的两个实体类,批量获取发生数据变更的属性信息。</p>
<p>我们最终想要实现的是用户可以看到关于某个表单的字段属性数据变化的过程,而我们定义在 C# 类中的属性有时候需要与实际页面上显示的字段名称进行映射,以及某些属性其实没有必要记录数据变化的情况,这里我通过添加自定义特性的方式,完善功能的实现。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 为指定的属性设定数据变更记录
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> PropertyChangeTrackingAttribute : Attribute
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 指定 PropertyChangeTrackingAttribute 属性的默认值
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> PropertyChangeTrackingAttribute Default = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> PropertyChangeTrackingAttribute();
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 构造一个新的 PropertyChangeTrackingAttribute 特性实例
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> PropertyChangeTrackingAttribute()
{ }
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 构造一个新的 PropertyChangeTrackingAttribute 特性实例
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="ignore"></span><span style="color: rgba(0, 128, 0, 1)">是否忽略该字段的数据变化</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> PropertyChangeTrackingAttribute(<span style="color: rgba(0, 0, 255, 1)">bool</span> ignore = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
{
IgnoreValue </span>=<span style="color: rgba(0, 0, 0, 1)"> ignore;
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 构造一个新的 PropertyChangeTrackingAttribute 特性实例
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="displayName"></span><span style="color: rgba(0, 128, 0, 1)">属性对应页面显示名称</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> PropertyChangeTrackingAttribute(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> displayName)
: </span><span style="color: rgba(0, 0, 255, 1)">this</span>(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
{
DisplayNameValue </span>=<span style="color: rgba(0, 0, 0, 1)"> displayName;
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 构造一个新的 PropertyChangeTrackingAttribute 特性实例
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="displayName"></span><span style="color: rgba(0, 128, 0, 1)">属性对应页面显示名称</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="ignore"></span><span style="color: rgba(0, 128, 0, 1)">是否忽略该字段的数据变化</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> PropertyChangeTrackingAttribute(<span style="color: rgba(0, 0, 255, 1)">string</span> displayName, <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> ignore)
: </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">(ignore)
{
DisplayNameValue </span>=<span style="color: rgba(0, 0, 0, 1)"> displayName;
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 获取特性中的属性对应页面上显示名称参数信息
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> <span style="color: rgba(0, 0, 255, 1)">string</span> DisplayName =><span style="color: rgba(0, 0, 0, 1)"> DisplayNameValue;
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 获取特性中的是否忽略该字段的数据变化参数信息
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> Ignore =><span style="color: rgba(0, 0, 0, 1)"> IgnoreValue;
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 修改属性对应页面显示名称参数值
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">string</span> DisplayNameValue { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 修改是否忽略该字段的数据变化
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> IgnoreValue { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
</div>
<p>考虑到我们的类中可能会包含很多的属性信息,如果一个个的给属性添加特性会很麻烦,因此这里可以直接针对类添加该特性。同时,针对我们可能会排除类中的某些属性,或者设定属性在页面中显示的名称,这里我们可以针对特定的类属性进行单独添加特性。</p>
<p>完成了自定义特性之后,考虑到我们后续使用的方便,这里我采用创建扩展方法的形式来声明我们的函数方法,同时我在 PropertyChangelog 类中添加了 DisplayName 属性用来存放属性对应于页面上存放的名称,最终完成后的代码如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 获取类属性数据变化记录
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><typeparam name="T"></span><span style="color: rgba(0, 128, 0, 1)">监听的类类型</span><span style="color: rgba(128, 128, 128, 1)"></typeparam></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="oldObj"></span><span style="color: rgba(0, 128, 0, 1)">包含原始值的类</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="newObj"></span><span style="color: rgba(0, 128, 0, 1)">变更属性值后的类</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="propertyName"></span><span style="color: rgba(0, 128, 0, 1)">指定的属性名称</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><returns></returns></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> IEnumerable<PropertyChangelog<T>> GetPropertyLogs<T>(<span style="color: rgba(0, 0, 255, 1)">this</span> T oldObj, T newObj, <span style="color: rgba(0, 0, 255, 1)">string</span> propertyName = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
{
IList</span><PropertyChangelog<T>> changelogs = <span style="color: rgba(0, 0, 255, 1)">new</span> List<PropertyChangelog<T>><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1、获取需要添加数据变更记录的属性信息
</span><span style="color: rgba(0, 128, 0, 1)">//
</span> IList<PropertyInfo> properties = <span style="color: rgba(0, 0, 255, 1)">new</span> List<PropertyInfo><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> PropertyChangeTracking 特性的类型</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> attributeType = <span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(PropertyChangeTrackingAttribute);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 对应的类中包含的属性信息</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> classProperties = <span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(T).GetProperties();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取类中需要添加变更记录的属性信息
</span><span style="color: rgba(0, 128, 0, 1)">//
</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> flag = Attribute.IsDefined(<span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(T), attributeType);
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> i <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> classProperties)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取当前属性添加的特性信息</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> attributeInfo =<span style="color: rgba(0, 0, 0, 1)"> (PropertyChangeTrackingAttribute)i.GetCustomAttribute(attributeType);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 类未添加特性,并且该属性也未添加特性</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (!flag && attributeInfo == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 类添加特性,该属性未添加特性</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (flag && attributeInfo == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
properties.Add(i);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 不管类有没有添加特性,只要类中的属性添加特性,并且 Ignore 为 false</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (attributeInfo != <span style="color: rgba(0, 0, 255, 1)">null</span> && !<span style="color: rgba(0, 0, 0, 1)">attributeInfo.Ignore)
properties.Add(i);
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2、判断指定的属性数据是否发生变更
</span><span style="color: rgba(0, 128, 0, 1)">//
</span> <span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> property <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> properties)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> oldValue = property.GetValue(oldObj) ?? <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> newValue = property.GetValue(newObj) ?? <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (oldValue.Equals(newValue))
</span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取当前属性在页面上显示的名称
</span><span style="color: rgba(0, 128, 0, 1)">//
</span> <span style="color: rgba(0, 0, 255, 1)">var</span> attributeInfo =<span style="color: rgba(0, 0, 0, 1)"> (PropertyChangeTrackingAttribute)property.GetCustomAttribute(attributeType);
</span><span style="color: rgba(0, 0, 255, 1)">string</span> displayName = attributeInfo == <span style="color: rgba(0, 0, 255, 1)">null</span> ?<span style="color: rgba(0, 0, 0, 1)"> property.Name
: attributeInfo.DisplayName;
changelogs.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> PropertyChangelog<T><span style="color: rgba(0, 0, 0, 1)">(property.Name, displayName, oldValue.ToString(), newValue.ToString()));
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">string</span>.IsNullOrEmpty(propertyName) ?<span style="color: rgba(0, 0, 0, 1)"> changelogs
: changelogs.Where(i </span>=><span style="color: rgba(0, 0, 0, 1)"> i.PropertyName.Equals(propertyName));
}</span></pre>
</div>
<p>在下面的这个测试案例中,Entity 类实际上只会记录 5 个属性的数据变化,我们手动创建两个 Entity 类实例,同时改变两个类实例对应的属性值。从我们运行的示意图中可以看到,虽然两个类实例的 Id 属性值不同,但是因为被我们手动忽略了,所以最终只显示我们设定的几个属性的变化信息。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Entity
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> Guid Id { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> OId { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> A { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">double</span> B { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> C { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> DateTime Date { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } =<span style="color: rgba(0, 0, 0, 1)"> DateTime.Now;
}</span></pre>
</div>
<p><img src="https://img2018.cnblogs.com/common/1310859/202002/1310859-20200207153236612-17692118.gif" alt=""></p>
<p> </p>
<h3>三、总结</h3>
<p>这一章是针对我之前在工作中遇到的一个问题,趁着假期考虑的一个解决方法,虽然只是一个小问题,但是还是挺有借鉴意义的,如果能够给你在日常的开发中提供些许的帮助,不胜荣幸。</p><br><br>
来源:https://www.cnblogs.com/danvic712/p/how-to-get-the-data-changed-properties-in-csharp-class.html
頁:
[1]