C#中如何監控類別屬性變更的程式碼案例分享
C#監控類別屬性的變更(大花貓動了哪些小玩具)
在使用EF更新資料庫實體時。很多時候我們想要的只是更新表格中的某一個或部分欄位。雖然可以透過設定來告訴上下文我們要更新的欄位。但一般我們都會把資料持久層封裝起來。透過泛型操作。而這時我們就無法得知應用層面修改了哪些欄位了。
最近也在學習EF,就剛好遇到了這個問題。當然,如果直接在應用程式層面使用,透過設定欄位的IsModified狀態就可以了。如下
db.Entry(model).Property(x => x.Token).IsModified = false;
# 可是,這僅限於學習和demo。正式開發中一般是不會把這種底層操作公開給應用層面的。都會把資料庫持久層進行封裝。然後透過實體工廠(倉庫)加實體泛型的方式提供增刪改查。
具體的可以參考《基於Entity Framework的Repository模式設計》之類的文章。
這類方式都有一個共同點,更新和刪除的時候都有如下類似程式碼:
public virtual void Update(TEntity TObject) { try { var entry = Context.Entry(TObject); Context.Set<TEntity>().Attach(TObject); entry.State = EntityState.Modified; } catch (OptimisticConcurrencyException ex) { throw ex; } }
# 個人理解:Update(TEntity TObject)透過傳遞一個實體到方法,然後附加到資料庫上下文,並將資料標記為修改狀態。然後進行的更新。
這種情況會對實體的所有欄位進行更新。那我們就需要保證這個實體是從資料庫查出來的,或是與資料庫的記錄是對應的上的。這在C/S結構中是沒有問題的,可問題是在B/S結構中呢?我們不可能把實體所有的欄位都打包,傳送到客戶端,然後客戶端修改在回到服務端,然後在呼叫倉庫方法更新吧。說個最簡單的,修改用戶密碼,我們只需要一個用戶ID,一個新密碼就可以了。或鎖定用戶帳號,只需要一個用戶ID,一個鎖定狀態,一個鎖定時間。這樣,我們不可能把整個使用者實體打包傳來傳去吧。有人說可以在儲存的時候先根據ID查一遍資料庫,然後再將修改過的屬性值附加上去後再更新就可以了。這就回到問題上了:在倉庫方法中只有泛型類型,而你在呼叫倉庫更新方法時傳遞的是一個實體類型。倉庫並不知道你是那個實體,並且更新了哪些欄位。
當然,透過觸發器我們知道資料庫的更新都是先刪後插,所以更新幾個欄位與全列更新底層操作是沒有太多區別的。
現在拋開倉庫更新等實體泛型等資訊。就單看一下當一個實體改變時,我們怎麼能知道他修改了哪些屬性。
正常情況下一個實體長這樣
1 /// <summary> 2 /// 一个具体的实体 3 /// </summary> 4 public class AccountEntity : MainEntity 5 { 6 /// <summary> 7 /// 文本类型 8 /// </summary> 9 public virtual string Account { get; set; } 10 /// <summary> 11 /// 又一个文本属性 12 /// </summary> 13 public virtual string Password { get; set; } 14 /// <summary> 15 /// 数字类型 16 /// </summary> 17 public virtual int Sex { get; set; } 18 /// <summary> 19 /// 事件类型 20 /// </summary> 21 public virtual DateTime Birthday { get; set; } 22 /// <summary> 23 /// 双精度浮点数 24 /// </summary> 25 public virtual double Height { get; set; } 26 /// <summary> 27 /// 十进制数 28 /// </summary> 29 public virtual decimal Monery { get; set; } 30 /// <summary> 31 /// 二进制 32 /// </summary> 33 public virtual byte[] PublicKey { get; set; } 34 /// <summary> 35 /// Guid类型 36 /// </summary> 37 public virtual Guid AreaId { get; set; } 38 }
View Code
當我們要修改這個實體的屬性:
var entity = new accountEntity(); entity.Id=1; entity.Account = "给属性赋值';
接著將這個實體傳遞到底層進行操作。
db.Update(entity);
完全沒有問題,可是我的問題在底層怎麼知道我應用層修改了那幾個屬性呢?再加一個方法,告訴底層,我修改了這幾個屬性。
db.Update(entity,"Account");
好像也沒有什麼不可哈。
可是這樣,如果我修改了Account,參數中卻傳遞了Password怎麼辦?所以,應該在實體上就應該有一個集合對整個屬性是否有修改的狀態進行儲存。然後到底層Update方法在取出更新過的欄位進行下一步操作。
透過這個思路,我想到在實體中加一個字典:
protected Dictionary<string, dynamic> FieldTracking = new Dictionary<string, dynamic>();
當屬性賦值時,則加到字典中來。 (當然,這種操作是會增加程式的開銷的)
FieldTracking["Account"]="给属性赋值";
然后在底层在取出里面的集合,来区分哪些字段被修改(大花猫动了哪些小玩具)。
改造下实体属性
public virtual string Account { get { return _Account; } set { _Account = value; FieldTracking["Account"] = value; } }
看过编译后的IL代码的都知道,class中的属性最终会编译成两个方法 setvalue和getvalue,那么通过修改set方法添加FieldTracking["Account"] = value;就可以让属性在赋值的时候添加到字典中。
很简单吧。
你以为这样就完了。如果拿房间来比喻实体、拿玩具来比作属性。我家那大花猫就是修改实体属性的方法。你知道我家有多少玩具吗?你每天回家的时候你知道大花猫动了哪个小玩具吗?给每个玩具装个GPS?哈哈哈哈,别闹,花这心思还不如再买点回来。什么?买回来的还得装,算了。研究下怎么装吧。
一个程序可能有上百个实体类,修改现有的实体类,给每个set加一行?作为一个程序员是不可能容忍做这样的操作的。写一个工具,读取所有的实体代码,加上这一行,保存。这是个好办法。那每次添加一个实体类就得调用工具重写来一遍,每次修改属性再调用一遍,恩。没问题。能用就行。这不是一个真心养猫的人的人能容忍的。
那怎么办?把猫打死?那玩具的存在将会没有任何意义。想到一个办法,在我离开房子的时候(程序初始化),给房子里的所有房间(实体类)创建一个同样的房间(继承),包含了与原房间所有需要监控(标记为virtual)的玩具的复制,在复制过程中加上GPS(-_~)。然后给猫玩。猫通过我给的门进到这个继承的房间中玩所有玩具的时候,GPS就能将猫的动作全部记录下来。我一回家,这猫玩了哪些玩具一看GPS记录就全知道了。哟,这小崽子,在王元鹅呢。
看不懂,没关系,上马:
1、在程序集初始化的时候,通过反射,查找所有继承自BaseEntity的实体类。遍历其中的属性。找到标记为virtual进行复制。
刚开始对于如果找到virtual属性花了不少时间。我总只想着在属性上找,却没想到去set_value方法上去找(其实get_value方法也是)。还是太菜啊。
注:NoMapAttribute特性是一个自定义的标记,表示不参与映射。因为不参与映射就不需要监控。与本文章代码没有太大的关系。仅供参考。
//获取实体所在的程序集(ClassLibraryDemo) var assemblyArray = AppDomain.CurrentDomain.GetAssemblies() .Where(w => w.GetName().Name == "ClassLibraryDemo") .ToList(); //实体的基类 var baseEntityType = typeof(BaseEntity); //循环程序集 foreach (Assembly item in assemblyArray) { //找到这个程序集中继承自基类的实体 var types = item.GetTypes().Where(t => t.IsAbstract == false && baseEntityType.IsAssignableFrom(t) && t != baseEntityType); foreach (Type btItem in types){ //遍历这个实体类中的属性 var properties = btItem.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(w => w.CanRead && w.CanWrite && w.GetCustomAttributes(typeof(NoMapAttribute), false).Any() == false //TODO:要不要检查get方法? && w.GetSetMethod().IsVirtual); } }
2、根据1的结果,复制一个新的房间(动态代码生成一个类,这个类继承1中的实体,并且重写了属性的set方法)
这个过程就设计到动态代码的生成了。
//首先创建一个与实体类对应的动态类 CodeTypeDeclaration ct = new CodeTypeDeclaration(btItem.Name + "_Dynamic"); //循环实体中的所有标记为virtual的属性 foreach (PropertyInfo fiItem in properties) { //创建一个属性 var p = new CodeMemberProperty(); //设置属性为公共、重写 p.Attributes = MemberAttributes.Public | MemberAttributes.Override;//override //设置属性的类型为继承的属性的数据类型 p.Type = new CodeTypeReference(fiItem.PropertyType); //属性名称与继承的一致 p.Name = fiItem.Name; //包含set代码 p.HasSet = true; //包含get代码 p.HasGet = true; //设置get代码 //return base.Account p.GetStatements.Add(new CodeMethodReturnStatement( new CodeFieldReferenceExpression( new CodeBaseReferenceExpression(), fiItem.Name))); //设置set代码 //base.Account=value; p.SetStatements.Add( new CodeAssignStatement( new CodeFieldReferenceExpression( new CodeBaseReferenceExpression(), fiItem.Name), new CodePropertySetValueReferenceExpression())); //FieldTracking["Account"]=value; p.SetStatements.Add(new CodeSnippetExpression("FieldTracking[\"" + fiItem.Name + "\"] = value")); //将属性添加到类中 ct.Members.Add(p); }
3、将刚才生成的类加到原类所在的命名空间+".Dynamic"(加后缀以示区分)
//声明一个命名空间(与当前实体类同名+后缀) CodeNamespace ns = new CodeNamespace(btItem.Namespace + ".Dynamic"); ns.Types.Add(ct);
4、编辑生成代码所在的程序集
//要动态生成代码的程序集 CodeCompileUnit program = new CodeCompileUnit(); //添加引用 program.ReferencedAssemblies.Add("mscorlib.dll"); program.ReferencedAssemblies.Add("System.dll"); program.ReferencedAssemblies.Add("System.Core.dll"); //定义代码工厂 CSharpCodeProvider provider = new CSharpCodeProvider(); //编译程序集 var cr = provider.CompileAssemblyFromDom(new System.CodeDom.Compiler.CompilerParameters(); //看编译是否通过 var error = cr.Errors; if (error.HasErrors) { Console.WriteLine("错误列表:"); //编译不通过 foreach (dynamic item in error) { Console.WriteLine("ErrorNumber:{0};Line:{1};ErrorText{2}", item.ErrorNumber, item.Line, item.ErrorText); } return; } else { Console.WriteLine("编译成功。"); }
查看生成的代码
//查看生成的代码 var codeText = new StringBuilder(); using (var codeWriter = new StringWriter(codeText)) { CodeDomProvider.CreateProvider("CSharp").GenerateCodeFromNamespace(ns, codeWriter, new CodeGeneratorOptions() { BlankLinesBetweenMembers = true }); } Console.WriteLine(codeText);
5、将复制的新类与原类建立映射关系。
foreach (Type item in ts) { //注册(模拟实现,通过字典实现的,也可以通过IOC注入方式处理) Mapping.Map(item.BaseType, item); }
6、获得这个复制的实体对象
//创建一个指定的实体对象 AccountEntity ae = Mapping.GetMap<AccountEntity>();
7、对这个实体对象的属性进行赋值
//主键赋值不会修改属性更新 ae.BaseEntity_Id = 1;//不会变(未标记为virtual) ae.MainEntity_Name = "大花猫"; ae.MainEntity_UpdateTime = DateTime.Now; //修改某个属性 ae.Account = "admin"; ae.Account = "以最后一次的修改为准";
8、调用底层方法,底层根据这个实体属性获得被修改的属性名称
//调用基类中的方法 获取变动的属性 var up = ae.GetFieldTracking(); Console.WriteLine("有修改的字段:"); up.ForEach(fe => { Console.WriteLine(fe + ":" + ae[fe]); });
9、完美
就这样,在底层就能知道哪些实体被赋值过了。
当然,有些实体我们只是需要用来计算,则可以调用方法将赋值过的属性进行删除
//删除变更字段 ae.RemoveChanges("Account");
这只是一个简单的实现,还有一种比较复杂的情况,在第6步,获得这个复制的实体对象时,怎么用一个现有的new出来的实体对象去创建建并监控呢。就像,别人送我一房间现成的玩具,给我的时候猫就在里面玩了。嗷,把猫打死吧。
总结:
再次认识到反射的强大。
也第一次实现了代码生成代码并使用的经历。
对字段和属性的区别有了更深的认识。
对访问修饰符和虚virtual方法有了更好的认识。
以上是C#中如何監控類別屬性變更的程式碼案例分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

有的用戶在安裝設備的時候遇到了錯誤,提示錯誤代碼28,其實這主要是由於驅動程式的原因,我們只要解決win7驅動程式碼28的問題就可以了,下面就一起來看一下應該怎麼來操作吧。 win7驅動程式碼28怎麼辦:首先,我們需要點擊螢幕左下角的開始選單。接著,在彈出的選單中找到並點擊“控制面板”選項。這個選項通常位於選單的底部或附近。點擊後,系統會自動開啟控制面板介面。在控制面板中,我們可以進行各種系統設定和管理操作。這是懷舊大掃除關卡中的第一步,希望對大家有幫助。然後,我們需要繼續操作,進入系統和

藍屏代碼0x0000001怎麼辦藍屏錯誤是電腦系統或硬體出現問題時的一種警告機制,代碼0x0000001通常表示出現了硬體或驅動程式故障。當使用者在使用電腦時突然遇到藍色畫面錯誤,可能會感到驚慌失措。幸運的是,大多數藍色畫面錯誤都可以透過一些簡單的步驟來排除和處理。本文將為讀者介紹一些解決藍屏錯誤代碼0x0000001的方法。首先,當遇到藍色畫面錯誤時,我們可以嘗試重

win10系統是一款非常優秀的高智慧系統強大的智慧可以為使用者帶來最好的使用體驗,一般正常的情況下使用者的win10系統電腦都不會出現任何的問題!但在優秀的電腦也難免會出現各種故障最近一直有小伙伴們反應自己的win10系統遇到了頻繁藍屏的問題!今天小編就為大家帶來了win10電腦頻繁藍屏不同代碼的解決方法讓我們一起來看看吧。電腦頻繁藍屏而且每次代碼不一樣的解決辦法:造成各種故障碼的原因以及解決建議1、0×000000116故障原因:應該是顯示卡驅動不相容。解決建議:建議更換廠商原帶驅動。 2、

終止代碼0xc000007b在使用電腦時,有時會遇到各種各樣的問題和錯誤代碼。其中,終止代碼最為令人困擾,尤其是終止代碼0xc000007b。這個程式碼表示某個應用程式無法正常啟動,給用戶帶來了不便。首先,我們來了解終止碼0xc000007b的意思。這個程式碼是Windows作業系統的錯誤代碼,通常發生在32位元應用程式嘗試在64位元作業系統上執行時。它表示應

如果您需要遠端編程任何設備,這篇文章會為您帶來幫助。我們將分享編程任何設備的頂級GE通用遠端代碼。通用電氣的遙控器是什麼? GEUniversalRemote是一款遙控器,可用於控制多個設備,如智慧電視、LG、Vizio、索尼、藍光、DVD、DVR、Roku、AppleTV、串流媒體播放器等。 GEUniversal遙控器有各種型號,具有不同的功能和功能。 GEUniversalRemote最多可以控制四台設備。頂級通用遙控器代碼,可在任何裝置上編程GE遙控器配備一組代碼,使其能夠與不同設備配合。您可

藍屏是我們在系統使用的時候常常會碰到的問題,根據錯誤代碼的不同,會有很多中不一樣的原因和解決方法。例如我們在使用時遇到stop:0x0000007f的問題,可能是硬體或軟體錯誤,下面就跟著小編一起來看看解決方法吧。 0x000000c5藍色畫面代碼原因:答:記憶體、CPU、顯示卡突然超頻,或軟體運作錯誤。解決方法一:1.開機時不斷按F8進入,選擇安全模式,回車進入。 2.進入安全模式後,按win+r開啟運行窗口,輸入cmd,回車。 3.在指令提示窗口,輸入“chkdsk/f/r”,回車,然後按y鍵。 4、

0x000000d1藍屏程式碼是什麼意思近年來,隨著電腦的普及和網路的快速發展,作業系統的穩定性和安全性問題也日益凸顯。一個常見的問題是藍色畫面錯誤,代碼0x000000d1是其中之一。藍色畫面錯誤,或稱為“藍色畫面死機”,是當電腦遇到嚴重系統故障時發生的情況。當系統無法從錯誤中恢復時,Windows作業系統會顯示一個藍色的螢幕,並在螢幕上顯示錯誤代碼。這些錯誤代

快速上手Python繪圖:畫出冰墩墩的程式碼範例Python是一種簡單易學且功能強大的程式語言,透過使用Python的繪圖庫,我們可以輕鬆實現各種繪圖需求。在本篇文章中,我們將使用Python的繪圖庫matplotlib來畫出冰墩墩的簡單圖形。冰墩墩是一隻擁有可愛形象的熊貓,非常受小朋友的喜愛。首先,我們需要安裝matplotlib函式庫。你可以透過在終端運行
