詳解如何獲取C#類中發(fā)生數(shù)據(jù)變化的屬性信息
一、前言#
在平時的開發(fā)中,當(dāng)用戶修改數(shù)據(jù)時,一直沒有很好的辦法來記錄具體修改了那些信息,只能暫時采用將類序列化成 json 字符串,然后全塞入到日志中的方式,此時如果我們想要知道用戶具體改變了哪幾個字段的值的話就很困難了。因此,趁著這個假期,就來解決這個一直遺留的小問題,本篇文章記錄了我目前實現(xiàn)的方法,如果你有不同于文中所列出的方案的話,歡迎指出。
代碼倉儲地址:https://github.com/Lanesra712/ingos-common/tree/master/sample/csharp/get-data-changed-properties
二、Step by Step#
1、需求場景#
一個經(jīng)常遇到的使用場景,用戶 A 修改了某個表單頁面上的數(shù)據(jù)信息,然后提交到我們的服務(wù)端完成數(shù)據(jù)的更新,對于具有某些權(quán)限的用戶來說,則是期望可以看到所有用戶對于該表單進(jìn)行操作前后的數(shù)據(jù)變更。
2、解決方法#
既然想要得知用戶操作前后的數(shù)據(jù)差異,我們肯定需要去對用戶操作前后的數(shù)據(jù)進(jìn)行比對,這里就落到我們承接數(shù)據(jù)的類身上。
在我們定義類中的屬性時,更多的是使用自動屬性的方式來完成屬性的 getter、setter 聲明,而完整的屬性聲明方式則需要我們定義一個字段用來承接對于該屬性的變更。
// 自動屬性聲明 public class Entity1 { public Guid Id { get; set; } } // 完整的屬性聲明 public class Entity2 { private Guid _id; public Guid Id { get => _id; set => _id = value; } }
因為在給屬性進(jìn)行賦值的時候,需要調(diào)用屬性的 set 構(gòu)造器,因此,在 set 構(gòu)造器內(nèi)部我們是不是就可以直接對新賦的值進(jìn)行判斷,從而記錄下屬性的變更過程,改造后的類屬性聲明代碼如下。
public class Sample { private string _a; public string A { get => _a; set { if (_a == value) return; string old = _a; _a = value; propertyChangelogs.Add(new PropertyChangelog<Sample>(nameof(A), old, _a)); } } private double _b; public double B { get => _b; set { if (_b == value) return; double old = _b; _b = value; propertyChangelogs.Add(new PropertyChangelog<Sample>(nameof(B), old.ToString(), _b.ToString())); } } private IList<PropertyChangelog<Sample>> propertyChangelogs = new List<PropertyChangelog<Sample>>(); public IEnumerable<PropertyChangelog<Sample>> Changelogs() => propertyChangelogs; }
在改造后的類屬性聲明中,我們在屬性的 set 構(gòu)造器中將新賦的值與原先的值進(jìn)行判斷,當(dāng)存在兩次值不一樣時,就寫入到變更記錄的集合中,從而實現(xiàn)記錄數(shù)據(jù)變更的目的。這里對于變更記錄的實體類屬性定義如下所示。
public class PropertyChangelog<T> { /// <summary> /// ctor /// </summary> public PropertyChangelog() { } /// <summary> /// ctor /// </summary> /// <param name="propertyName">屬性名稱</param> /// <param name="oldValue">舊值</param> /// <param name="newValue">新值</param> public PropertyChangelog(string propertyName, string oldValue, string newValue) { PropertyName = propertyName; OldValue = oldValue; NewValue = newValue; } /// <summary> /// ctor /// </summary> /// <param name="className">類名</param> /// <param name="propertyName">屬性名稱</param> /// <param name="oldValue">舊值</param> /// <param name="newValue">新值</param> /// <param name="changedTime">修改時間</param> public PropertyChangelog(string className, string propertyName, string oldValue, string newValue, DateTime changedTime) : this(propertyName, oldValue, newValue) { ClassName = className; ChangedTime = changedTime; } /// <summary> /// 類名稱 /// </summary> public string ClassName { get; set; } = typeof(T).FullName; /// <summary> /// 屬性名稱 /// </summary> public string PropertyName { get; set; } /// <summary> /// 舊值 /// </summary> public string OldValue { get; set; } /// <summary> /// 新值 /// </summary> public string NewValue { get; set; } /// <summary> /// 修改時間 /// </summary> public DateTime ChangedTime { get; set; } = DateTime.Now; }
可以看到,在我們對 Sample 類進(jìn)行初始化賦值時,記錄了兩次關(guān)于類屬性的數(shù)據(jù)變更記錄,而當(dāng)我們進(jìn)行重新賦值時,只有屬性 A 發(fā)生了數(shù)據(jù)改變,因此只記錄了屬性 A 的數(shù)據(jù)變更記錄。
雖然這里已經(jīng)達(dá)到我們的目的,但是如果采用這種方式的話,相當(dāng)于原先項目中需要實現(xiàn)數(shù)據(jù)記錄功能的類的屬性聲明方式全部需要重寫,同時,基于 C# 本身已經(jīng)提供了自動屬性的方式來簡化屬性聲明,結(jié)果現(xiàn)在我們又回到了傳統(tǒng)屬性的聲明方式,似乎顯得有些不太聰明的樣子。因此,既然通過一個個屬性進(jìn)行比較的方式過于繁瑣,這里我們通過反射的方式直接對比修改前后的兩個實體類,批量獲取發(fā)生數(shù)據(jù)變更的屬性信息。
我們最終想要實現(xiàn)的是用戶可以看到關(guān)于某個表單的字段屬性數(shù)據(jù)變化的過程,而我們定義在 C# 類中的屬性有時候需要與實際頁面上顯示的字段名稱進(jìn)行映射,以及某些屬性其實沒有必要記錄數(shù)據(jù)變化的情況,這里我通過添加自定義特性的方式,完善功能的實現(xiàn)。
/// <summary> /// 為指定的屬性設(shè)定數(shù)據(jù)變更記錄 /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] public class PropertyChangeTrackingAttribute : Attribute { /// <summary> /// 指定 PropertyChangeTrackingAttribute 屬性的默認(rèn)值 /// </summary> public static readonly PropertyChangeTrackingAttribute Default = new PropertyChangeTrackingAttribute(); /// <summary> /// 構(gòu)造一個新的 PropertyChangeTrackingAttribute 特性實例 /// </summary> public PropertyChangeTrackingAttribute() { } /// <summary> /// 構(gòu)造一個新的 PropertyChangeTrackingAttribute 特性實例 /// </summary> /// <param name="ignore">是否忽略該字段的數(shù)據(jù)變化</param> public PropertyChangeTrackingAttribute(bool ignore = false) { IgnoreValue = ignore; } /// <summary> /// 構(gòu)造一個新的 PropertyChangeTrackingAttribute 特性實例 /// </summary> /// <param name="displayName">屬性對應(yīng)頁面顯示名稱</param> public PropertyChangeTrackingAttribute(string displayName) : this(false) { DisplayNameValue = displayName; } /// <summary> /// 構(gòu)造一個新的 PropertyChangeTrackingAttribute 特性實例 /// </summary> /// <param name="displayName">屬性對應(yīng)頁面顯示名稱</param> /// <param name="ignore">是否忽略該字段的數(shù)據(jù)變化</param> public PropertyChangeTrackingAttribute(string displayName, bool ignore) : this(ignore) { DisplayNameValue = displayName; } /// <summary> /// 獲取特性中的屬性對應(yīng)頁面上顯示名稱參數(shù)信息 /// </summary> public virtual string DisplayName => DisplayNameValue; /// <summary> /// 獲取特性中的是否忽略該字段的數(shù)據(jù)變化參數(shù)信息 /// </summary> public virtual bool Ignore => IgnoreValue; /// <summary> /// 修改屬性對應(yīng)頁面顯示名稱參數(shù)值 /// </summary> protected string DisplayNameValue { get; set; } /// <summary> /// 修改是否忽略該字段的數(shù)據(jù)變化 /// </summary> protected bool IgnoreValue { get; set; } }
考慮到我們的類中可能會包含很多的屬性信息,如果一個個的給屬性添加特性會很麻煩,因此這里可以直接針對類添加該特性。同時,針對我們可能會排除類中的某些屬性,或者設(shè)定屬性在頁面中顯示的名稱,這里我們可以針對特定的類屬性進(jìn)行單獨(dú)添加特性。
完成了自定義特性之后,考慮到我們后續(xù)使用的方便,這里我采用創(chuàng)建擴(kuò)展方法的形式來聲明我們的函數(shù)方法,同時我在 PropertyChangelog 類中添加了 DisplayName 屬性用來存放屬性對應(yīng)于頁面上存放的名稱,最終完成后的代碼如下所示。
/// <summary> /// 獲取類屬性數(shù)據(jù)變化記錄 /// </summary> /// <typeparam name="T">監(jiān)聽的類類型</typeparam> /// <param name="oldObj">包含原始值的類</param> /// <param name="newObj">變更屬性值后的類</param> /// <param name="propertyName">指定的屬性名稱</param> /// <returns></returns> public static IEnumerable<PropertyChangelog<T>> GetPropertyLogs<T>(this T oldObj, T newObj, string propertyName = null) { IList<PropertyChangelog<T>> changelogs = new List<PropertyChangelog<T>>(); // 1、獲取需要添加數(shù)據(jù)變更記錄的屬性信息 // IList<PropertyInfo> properties = new List<PropertyInfo>(); // PropertyChangeTracking 特性的類型 var attributeType = typeof(PropertyChangeTrackingAttribute); // 對應(yīng)的類中包含的屬性信息 var classProperties = typeof(T).GetProperties(); // 獲取類中需要添加變更記錄的屬性信息 // bool flag = Attribute.IsDefined(typeof(T), attributeType); foreach (var i in classProperties) { // 獲取當(dāng)前屬性添加的特性信息 var attributeInfo = (PropertyChangeTrackingAttribute)i.GetCustomAttribute(attributeType); // 類未添加特性,并且該屬性也未添加特性 if (!flag && attributeInfo == null) continue; // 類添加特性,該屬性未添加特性 if (flag && attributeInfo == null) properties.Add(i); // 不管類有沒有添加特性,只要類中的屬性添加特性,并且 Ignore 為 false if (attributeInfo != null && !attributeInfo.Ignore) properties.Add(i); } // 2、判斷指定的屬性數(shù)據(jù)是否發(fā)生變更 // foreach (var property in properties) { var oldValue = property.GetValue(oldObj) ?? ""; var newValue = property.GetValue(newObj) ?? ""; if (oldValue.Equals(newValue)) continue; // 獲取當(dāng)前屬性在頁面上顯示的名稱 // var attributeInfo = (PropertyChangeTrackingAttribute)property.GetCustomAttribute(attributeType); string displayName = attributeInfo == null ? property.Name : attributeInfo.DisplayName; changelogs.Add(new PropertyChangelog<T>(property.Name, displayName, oldValue.ToString(), newValue.ToString())); } return string.IsNullOrEmpty(propertyName) ? changelogs : changelogs.Where(i => i.PropertyName.Equals(propertyName)); }
在下面的這個測試案例中,Entity 類實際上只會記錄 5 個屬性的數(shù)據(jù)變化,我們手動創(chuàng)建兩個 Entity 類實例,同時改變兩個類實例對應(yīng)的屬性值。從我們運(yùn)行的示意圖中可以看到,雖然兩個類實例的 Id 屬性值不同,但是因為被我們手動忽略了,所以最終只顯示我們設(shè)定的幾個屬性的變化信息。
[PropertyChangeTracking] public class Entity { [PropertyChangeTracking(ignore: true)] public Guid Id { get; set; } [PropertyChangeTracking(displayName: "序號")] public string OId { get; set; } [PropertyChangeTracking(displayName: "第一個字段")] public string A { get; set; } public double B { get; set; } public bool C { get; set; } public DateTime Date { get; set; } = DateTime.Now; }
三、總結(jié)#
這一章是針對我之前在工作中遇到的一個問題,趁著假期考慮的一個解決方法,雖然只是一個小問題,但是還是挺有借鑒意義的,如果能夠給你在日常的開發(fā)中提供些許的幫助,不勝榮幸。
作者:墨墨墨墨小宇
出處:https://www.cnblogs.com/danvic712/p/how-to-get-the-data-changed-properties-in-csharp-class.html
到此這篇關(guān)于詳解如何獲取C#類中發(fā)生數(shù)據(jù)變化的屬性信息的文章就介紹到這了,更多相關(guān)C#獲取類屬性信息內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- c#連接sqlserver數(shù)據(jù)庫、插入數(shù)據(jù)、從數(shù)據(jù)庫獲取時間示例
- C#從DataTable獲取數(shù)據(jù)的方法
- c# 獲取數(shù)據(jù)庫中所有表名稱的方法
- C#操作DataTable方法實現(xiàn)過濾、取前N條數(shù)據(jù)及獲取指定列數(shù)據(jù)列表的方法
- C#使用SqlDataAdapter對象獲取數(shù)據(jù)的方法
- C#實現(xiàn)解析百度天氣數(shù)據(jù),Rss解析百度新聞以及根據(jù)IP獲取所在城市的方法
- C#獲取所有SQL Server數(shù)據(jù)庫名稱的方法
- 在C#中如何使用正式表達(dá)式獲取匹配所需數(shù)據(jù)
- C#中獲取數(shù)據(jù)的方法實例
相關(guān)文章
C#使用channel實現(xiàn)Plc異步任務(wù)之間的通信
在C#的并發(fā)編程中,Channel是一種非常強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),用于在生產(chǎn)者和消費(fèi)者之間進(jìn)行通信,本文將給大家介紹C#使用channel實現(xiàn)Plc異步任務(wù)之間的通信,文中有相關(guān)的代碼示例供大家參考,感興趣的朋友跟著小編一起來看看吧2024-05-05C#使用TensorFlow.NET訓(xùn)練自己的數(shù)據(jù)集的方法
這篇文章主要介紹了C#使用TensorFlow.NET訓(xùn)練自己的數(shù)據(jù)集的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03使用GPS經(jīng)緯度定位附近地點(某一點范圍內(nèi)查詢)
目前的工作是需要手機(jī)查找附近N米以內(nèi)的商戶,致想法是已知一個中心點,一個半徑,求圓包含于圓拋物線里所有的點,經(jīng)緯度是一個點,半徑是一個距離,不能直接加減,下面提供C#的解決方法2013-12-12