搜尋
首頁後端開發C#.Net教程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 = "给属性赋值&#39;;

  接著將這個實體傳遞到底層進行操作。


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中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
將C#.NET應用程序部署到Azure/AWS:逐步指南將C#.NET應用程序部署到Azure/AWS:逐步指南Apr 23, 2025 am 12:06 AM

如何將C#.NET應用部署到Azure或AWS?答案是使用AzureAppService和AWSElasticBeanstalk。 1.在Azure上,使用AzureAppService和AzurePipelines自動化部署。 2.在AWS上,使用AmazonElasticBeanstalk和AWSLambda實現部署和無服務器計算。

C#.NET:強大的編程語言簡介C#.NET:強大的編程語言簡介Apr 22, 2025 am 12:04 AM

C#和.NET的結合為開發者提供了強大的編程環境。 1)C#支持多態性和異步編程,2).NET提供跨平台能力和並發處理機制,這使得它們在桌面、Web和移動應用開發中廣泛應用。

.NET框架與C#:解碼術語.NET框架與C#:解碼術語Apr 21, 2025 am 12:05 AM

.NETFramework是一個軟件框架,C#是一種編程語言。 1..NETFramework提供庫和服務,支持桌面、Web和移動應用開發。 2.C#設計用於.NETFramework,支持現代編程功能。 3..NETFramework通過CLR管理代碼執行,C#代碼編譯成IL後由CLR運行。 4.使用.NETFramework可快速開發應用,C#提供如LINQ的高級功能。 5.常見錯誤包括類型轉換和異步編程死鎖,調試需用VisualStudio工具。

揭開c#.net的神秘面紗:初學者的概述揭開c#.net的神秘面紗:初學者的概述Apr 20, 2025 am 12:11 AM

C#是一種由微軟開發的現代、面向對象的編程語言,.NET是微軟提供的開發框架。 C#結合了C 的性能和Java的簡潔性,適用於構建各種應用程序。 .NET框架支持多種語言,提供垃圾回收機制,簡化內存管理。

C#和.NET運行時:它們如何一起工作C#和.NET運行時:它們如何一起工作Apr 19, 2025 am 12:04 AM

C#和.NET運行時緊密合作,賦予開發者高效、強大且跨平台的開發能力。 1)C#是一種類型安全且面向對象的編程語言,旨在與.NET框架無縫集成。 2).NET運行時管理C#代碼的執行,提供垃圾回收、類型安全等服務,確保高效和跨平台運行。

C#.NET開發:入門的初學者指南C#.NET開發:入門的初學者指南Apr 18, 2025 am 12:17 AM

要開始C#.NET開發,你需要:1.了解C#的基礎知識和.NET框架的核心概念;2.掌握變量、數據類型、控制結構、函數和類的基本概念;3.學習C#的高級特性,如LINQ和異步編程;4.熟悉常見錯誤的調試技巧和性能優化方法。通過這些步驟,你可以逐步深入C#.NET的世界,並編寫高效的應用程序。

c#和.net:了解兩者之間的關係c#和.net:了解兩者之間的關係Apr 17, 2025 am 12:07 AM

C#和.NET的關係是密不可分的,但它們不是一回事。 C#是一門編程語言,而.NET是一個開發平台。 C#用於編寫代碼,編譯成.NET的中間語言(IL),由.NET運行時(CLR)執行。

c#.net的持續相關性:查看當前用法c#.net的持續相關性:查看當前用法Apr 16, 2025 am 12:07 AM

C#.NET依然重要,因為它提供了強大的工具和庫,支持多種應用開發。 1)C#結合.NET框架,使開發高效便捷。 2)C#的類型安全和垃圾回收機制增強了其優勢。 3).NET提供跨平台運行環境和豐富的API,提升了開發靈活性。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

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

熱工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中