Jadual Kandungan
  C#监控类属性的更改(大花猫动了哪些小玩具)
Rumah pembangunan bahagian belakang Tutorial C#.Net C#中如何监控类属性更改的代码案例分享

C#中如何监控类属性更改的代码案例分享

Jul 27, 2017 pm 04:15 PM
.net kod

  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;
            }
        }
Salin selepas log masuk

  个人理解: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     }
Salin selepas log masuk

View Code

  当我们要修改这个实体的属性时:


var entity = new accountEntity();
entity.Id=1;
entity.Account = "给属性赋值&#39;;
Salin selepas log masuk

  然后将这个实体传递到底层进行操作。


db.Update(entity);
Salin selepas log masuk

  完全没有问题,可是我的问题在底层怎么知道我应用层修改了那几个属性呢?再加一个方法,告诉底层,我修改了这几个属性。


db.Update(entity,"Account");
Salin selepas log masuk

  好像也没有什么不可哈。

  可是这样,如果我修改了Account,参数中却传递了Password怎么办?所以,应该在实体上就应该有一个集合对整个属性是否有修改的状态进行存储。然后到底层Update方法在取出更新过的字段进行下一步操作。
  通过这一思路,我想到在实体中加一个字典:


protected Dictionary<string, dynamic> FieldTracking = new Dictionary<string, dynamic>();
Salin selepas log masuk

  当属性赋值时,则添加到字典中来。(当然,这种操作是会增加程序的开销的)


FieldTracking["Account"]="给属性赋值";
Salin selepas log masuk

  然后在底层在取出里面的集合,来区分哪些字段被修改(大花猫动了哪些小玩具)。

  改造下实体属性


        public virtual string Account
        {
            get
            { return _Account; }
            set {
                _Account = value;
                FieldTracking["Account"] = value;
            }
        }
Salin selepas log masuk

  看过编译后的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);
    }
}
Salin selepas log masuk

  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);
}
Salin selepas log masuk

  3、将刚才生成的类加到原类所在的命名空间+".Dynamic"(加后缀以示区分)


//声明一个命名空间(与当前实体类同名+后缀)
CodeNamespace ns = new CodeNamespace(btItem.Namespace + ".Dynamic");
ns.Types.Add(ct);
Salin selepas log masuk

  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("编译成功。");
    }
Salin selepas log masuk

  查看生成的代码


//查看生成的代码
var codeText = new StringBuilder();
using (var codeWriter = new StringWriter(codeText))
{
    CodeDomProvider.CreateProvider("CSharp").GenerateCodeFromNamespace(ns,
        codeWriter,
        new CodeGeneratorOptions()
        {
            BlankLinesBetweenMembers = true
        });
}
Console.WriteLine(codeText);
Salin selepas log masuk

  5、将复制的新类与原类建立映射关系。


foreach (Type item in ts)
{
    //注册(模拟实现,通过字典实现的,也可以通过IOC注入方式处理)
    Mapping.Map(item.BaseType, item);
}
Salin selepas log masuk

  6、获得这个复制的实体对象


//创建一个指定的实体对象
AccountEntity ae = Mapping.GetMap<AccountEntity>();
Salin selepas log masuk

  7、对这个实体对象的属性进行赋值


//主键赋值不会修改属性更新
ae.BaseEntity_Id = 1;//不会变(未标记为virtual)
ae.MainEntity_Name = "大花猫";
ae.MainEntity_UpdateTime = DateTime.Now;
//修改某个属性
ae.Account = "admin";
ae.Account = "以最后一次的修改为准";
Salin selepas log masuk

  8、调用底层方法,底层根据这个实体属性获得被修改的属性名称


//调用基类中的方法 获取变动的属性
var up = ae.GetFieldTracking();
Console.WriteLine("有修改的字段:");
up.ForEach(fe =>
{
    Console.WriteLine(fe + ":" + ae[fe]);
});
Salin selepas log masuk

  9、完美

  

  就这样,在底层就能知道哪些实体被赋值过了。

  当然,有些实体我们只是需要用来计算,则可以调用方法将赋值过的属性进行删除


//删除变更字段
ae.RemoveChanges("Account");
Salin selepas log masuk

  这只是一个简单的实现,还有一种比较复杂的情况,在第6步,获得这个复制的实体对象时,怎么用一个现有的new出来的实体对象去创建建并监控呢。就像,别人送我一房间现成的玩具,给我的时候猫就在里面玩了。嗷,把猫打死吧。

  总结:

再次认识到反射的强大。
也第一次实现了代码生成代码并使用的经历。
对字段和属性的区别有了更深的认识。
对访问修饰符和虚virtual方法有了更好的认识。

 

Atas ialah kandungan terperinci C#中如何监控类属性更改的代码案例分享. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas

Tutorial Java
1664
14
Tutorial PHP
1268
29
Tutorial C#
1246
24
Bagaimana untuk menyelesaikan kod pemandu win7 28 Bagaimana untuk menyelesaikan kod pemandu win7 28 Dec 30, 2023 pm 11:55 PM

Sesetengah pengguna mengalami ralat semasa memasang peranti, menyebabkan kod ralat 28. Sebenarnya, ini disebabkan terutamanya oleh pemandu Kami hanya perlu menyelesaikan masalah kod pemandu win7 28. Mari kita lihat apa yang perlu dilakukan . Apa yang perlu dilakukan dengan kod pemandu win7 28: Pertama, kita perlu mengklik pada menu mula di sudut kiri bawah skrin. Kemudian, cari dan klik pilihan "Panel Kawalan" dalam menu pop timbul. Pilihan ini biasanya terletak di atau berhampiran bahagian bawah menu. Selepas mengklik, sistem akan membuka antara muka panel kawalan secara automatik. Dalam panel kawalan, kami boleh melakukan pelbagai tetapan sistem dan operasi pengurusan. Ini adalah langkah pertama dalam tahap pembersihan nostalgia, saya harap ia membantu. Kemudian kita perlu meneruskan dan memasuki sistem dan

Apa yang perlu dilakukan jika kod skrin biru 0x0000001 berlaku Apa yang perlu dilakukan jika kod skrin biru 0x0000001 berlaku Feb 23, 2024 am 08:09 AM

Apa yang perlu dilakukan dengan kod skrin biru 0x0000001 Ralat skrin biru adalah mekanisme amaran apabila terdapat masalah dengan sistem komputer atau perkakasan Kod 0x0000001 biasanya menunjukkan kegagalan perkakasan. Apabila pengguna tiba-tiba mengalami ralat skrin biru semasa menggunakan komputer mereka, mereka mungkin berasa panik dan rugi. Nasib baik, kebanyakan ralat skrin biru boleh diselesaikan dan ditangani dengan beberapa langkah mudah. Artikel ini akan memperkenalkan pembaca kepada beberapa kaedah untuk menyelesaikan kod ralat skrin biru 0x0000001. Pertama, apabila menghadapi ralat skrin biru, kita boleh cuba untuk memulakan semula

Komputer kerap berskrin biru dan kodnya berbeza setiap kali Komputer kerap berskrin biru dan kodnya berbeza setiap kali Jan 06, 2024 pm 10:53 PM

Sistem win10 ialah sistem kecerdasan tinggi yang sangat baik Kepintarannya yang berkuasa boleh membawa pengalaman pengguna yang terbaik kepada pengguna Dalam keadaan biasa, komputer sistem win10 pengguna tidak akan menghadapi sebarang masalah. Walau bagaimanapun, tidak dapat dielakkan bahawa pelbagai kerosakan akan berlaku pada komputer yang sangat baik Baru-baru ini, rakan-rakan telah melaporkan bahawa sistem win10 mereka sering menghadapi skrin biru. Hari ini, editor akan membawakan anda penyelesaian kepada kod berbeza yang menyebabkan skrin biru kerap berlaku dalam komputer Windows 10 Mari kita lihat. Penyelesaian kepada skrin biru komputer yang kerap dengan kod berbeza setiap kali: punca pelbagai kod kerosakan dan cadangan penyelesaian 1. Punca kerosakan 0×000000116: Seharusnya pemacu kad grafik tidak serasi. Penyelesaian: Adalah disyorkan untuk menggantikan pemacu pengilang asal. 2.

Program kod jauh universal GE pada mana-mana peranti Program kod jauh universal GE pada mana-mana peranti Mar 02, 2024 pm 01:58 PM

Jika anda perlu memprogramkan sebarang peranti dari jauh, artikel ini akan membantu anda. Kami akan berkongsi kod jauh universal GE teratas untuk pengaturcaraan sebarang peranti. Apakah alat kawalan jauh GE? GEUniversalRemote ialah alat kawalan jauh yang boleh digunakan untuk mengawal berbilang peranti seperti TV pintar, LG, Vizio, Sony, Blu-ray, DVD, DVR, Roku, AppleTV, pemain media penstriman dan banyak lagi. Alat kawalan jauh GEUniversal datang dalam pelbagai model dengan fungsi dan keupayaan yang berbeza. GEUniversalRemote boleh mengawal sehingga empat peranti. Kod Jauh Universal Teratas untuk Diprogramkan pada Mana-mana Peranti Alat kawalan jauh GE disertakan dengan set kod yang membolehkannya berfungsi dengan peranti yang berbeza. anda boleh

Cara menggunakan Copilot untuk menjana kod Cara menggunakan Copilot untuk menjana kod Mar 23, 2024 am 10:41 AM

Sebagai seorang pengaturcara, saya teruja dengan alatan yang memudahkan pengalaman pengekodan. Dengan bantuan alat kecerdasan buatan, kami boleh menjana kod demo dan membuat pengubahsuaian yang diperlukan mengikut keperluan. Alat Copilot yang baru diperkenalkan dalam Visual Studio Code membolehkan kami mencipta kod yang dijana AI dengan interaksi sembang bahasa semula jadi. Dengan menerangkan kefungsian, kami dapat memahami dengan lebih baik maksud kod sedia ada. Bagaimana untuk menggunakan Copilot untuk menjana kod? Untuk bermula, kami terlebih dahulu perlu mendapatkan sambungan PowerPlatformTools yang terkini. Untuk mencapai ini, anda perlu pergi ke halaman sambungan, cari "PowerPlatformTool" dan klik butang Pasang

Selesaikan ralat kod 0xc000007b Selesaikan ralat kod 0xc000007b Feb 18, 2024 pm 07:34 PM

Kod Penamatan 0xc000007b Semasa menggunakan komputer anda, kadangkala anda menghadapi pelbagai masalah dan kod ralat. Antaranya, kod penamatan adalah yang paling mengganggu terutamanya kod penamatan 0xc000007b. Kod ini menunjukkan bahawa aplikasi tidak boleh dimulakan dengan betul, menyebabkan ketidakselesaan kepada pengguna. Mula-mula, mari kita fahami maksud kod penamatan 0xc000007b. Kod ini ialah kod ralat sistem pengendalian Windows yang biasanya berlaku apabila aplikasi 32-bit cuba dijalankan pada sistem pengendalian 64-bit. Maksudnya sepatutnya

Penjelasan terperinci tentang punca dan penyelesaian kod skrin biru 0x0000007f Penjelasan terperinci tentang punca dan penyelesaian kod skrin biru 0x0000007f Dec 25, 2023 pm 02:19 PM

Skrin biru adalah masalah yang sering kita hadapi apabila menggunakan sistem Bergantung pada kod ralat, akan terdapat banyak sebab dan penyelesaian yang berbeza. Sebagai contoh, apabila kita menghadapi masalah berhenti: 0x0000007f, ia mungkin ralat perkakasan atau perisian Mari ikut editor untuk mengetahui penyelesaiannya. 0x000000c5 sebab kod skrin biru: Jawapan: Memori, CPU dan kad grafik tiba-tiba overclocked atau perisian berjalan dengan tidak betul. Penyelesaian 1: 1. Teruskan tekan F8 untuk masuk semasa but, pilih mod selamat, dan tekan Enter untuk masuk. 2. Selepas memasuki mod selamat, tekan win+r untuk membuka tetingkap jalankan, masukkan cmd, dan tekan Enter. 3. Dalam tetingkap command prompt, masukkan "chkdsk /f /r", tekan Enter, dan kemudian tekan kekunci y. 4.

Panduan ringkas untuk mempelajari lukisan Python: contoh kod untuk melukis kiub ais Panduan ringkas untuk mempelajari lukisan Python: contoh kod untuk melukis kiub ais Jan 13, 2024 pm 02:00 PM

Mulakan dengan pantas dengan lukisan Python: contoh kod untuk melukis Bingdundun Python ialah bahasa pengaturcaraan yang mudah dipelajari dan berkuasa Dengan menggunakan perpustakaan lukisan Python, kita boleh merealisasikan pelbagai keperluan lukisan dengan mudah. Dalam artikel ini, kami akan menggunakan pustaka lukisan Python matplotlib untuk melukis graf ringkas ais. Bingdundun ialah seekor panda comel yang sangat popular di kalangan kanak-kanak. Pertama, kita perlu memasang perpustakaan matplotlib. Anda boleh melakukan ini dengan berjalan di terminal

See all articles