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().Attach(TObject);                entry.State = EntityState.Modified;            }            catch (OptimisticConcurrencyException ex)            {                throw ex;            }        }

登录后复制

  个人理解:Update(TEntity TObject)通过传递一个实体到方法,然后附加到数据库上下文,并将数据标记为修改状态。然后进行的更新。
  这种情况会对实体的所有字段进行更新。那么我们则需要保证这个实体是从数据库查出来的,或者与数据库的记录是对应的上的。这在C/S结构中是没有问题的,可问题是在B/S结构中呢?我们不可能把实体所有的字段都打包,发送到客户端,然后客户端修改在返回到服务端,然后在调用仓库方法更新吧。说个最简单的,修改用户密码,我们只需要一个用户ID,一个新密码就可以了。或者锁定用户账号,只需要一个用户ID,一个锁定状态,一个锁定时间。这样,我们不可能把整个用户实体打包传来传去吧。有人说可以在保存的时候先根据ID查一遍数据库,然后再将修改的属性值附加上去后再更新就可以了。这就回到问题上了:在仓库方法中只有泛型类型,而你在调用仓库更新方法时传递的是一个实体类型。仓库并不知道你是那个实体,并且更新了哪些字段。
当然,通过触发器我们知道数据库的更新都是先删后插,所以更新几个字段与全列更新底层操作是没有多少区别的。

  现在抛开仓库更新等实体泛型等信息。就单看一下当一个实体发生改变时,我们怎么能知道他修改了哪些属性。
  正常情况下一个实体长这样

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

 1     ///   2     /// 一个具体的实体  3     ///   4     public class AccountEntity : MainEntity  5     {  6         ///   7         /// 文本类型  8         ///   9         public virtual string Account { get; set; } 10         ///  11         /// 又一个文本属性 12         ///  13         public virtual string Password { get; set; } 14         ///  15         /// 数字类型 16         ///  17         public virtual int Sex { get; set; } 18         ///  19         /// 事件类型 20         ///  21         public virtual DateTime Birthday { get; set; } 22         ///  23         /// 双精度浮点数 24         ///  25         public virtual double Height { get; set; } 26         ///  27         /// 十进制数 28         ///  29         public virtual decimal Monery { get; set; } 30         ///  31         /// 二进制 32         ///  33         public virtual byte[] PublicKey { get; set; } 34         ///  35         /// Guid类型 36         ///  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 FieldTracking = new Dictionary();

登录后复制

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

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.Accountp.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();

登录后复制

  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、完美

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

 

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

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

//删除变更字段ae.RemoveChanges("Account");

登录后复制

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

  总结:

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

 

以上就是C#中如何监控类属性更改的代码案例分享的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2439000.html

(0)
上一篇 2025年3月3日 11:54:58
下一篇 2025年2月26日 16:35:44

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 关于.NET后台定时服务框架的简单介绍

      后台服务只要是有一定经验的开发人员都接触过,其中离不开服务创建,调度逻辑处理,业务逻辑编写等环节。往往我们在新建一个后台服务项目的时候都会去拷贝以前的代码,再写一些线程等方式去完成,然后又去处理服务的安装问题。大部分时间都是浪费在这些重…

    2025年3月3日
    200
  • C#如何实现添加和修改以及删除PDF书签的方法介绍

    本篇文章主要介绍了c# 添加、修改和删除pdf书签的实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 有时候我们在阅读PDF文档时会遇到这样一种情况:PDF文档页数比较多,但是又没有书签,所以我们不能根据…

    2025年3月3日 编程技术
    200
  • .NET中关于接口和类之间的区别介绍

    在我们日常开发中经常会用到接口与类,这两者之间究竟有什么区别呢?又有哪些优缺点?下面这篇文章就来给大家介绍了关于.net中接口与类区别的相关资料,需要的朋友们可以参考借鉴,下面来一起看看吧。 前言 大家应该都知道,在.Net中提供了接口,这…

    编程技术 2025年3月3日
    200
  • 比较.NET中接口与类的区别

    在我们日常开发中经常会用到接口与类,这两者之间究竟有什么区别呢?又有哪些优缺点?下面这篇文章就来给大家介绍了关于.net中接口与类区别的相关资料,需要的朋友们可以参考借鉴,下面来一起看看吧。 前言 大家应该都知道,在.Net中提供了接口,这…

    编程技术 2025年3月3日
    200
  • C#如何连接加密数据库(Sqlite)的示例分享

    对数据加密分两种,一种是对数据库本身进行加密,另一种是对数据表中的数据进行加密,下面通过本文给大家介绍c#连接加密的sqlite数据库的方法,感兴趣的朋友一起看看吧 对数据加密分两种,一种是对数据库本身进行加密,另一种是对数据表中的数据进行…

    2025年3月3日
    200
  • Windows登录功能使用C#实现的示例

    这篇文章主要介绍了c#实现的windows登录功能,结合实例形式分析了简单的windows图形化登陆功能实现技巧,需要的朋友可以参考下 本文实例讲述了C#实现的WINDOWS登录功能。分享给大家供大家参考,具体如下: using Syste…

    编程技术 2025年3月3日
    200
  • C#编程如何获取电脑硬件信息的方法?

    这篇文章主要介绍了c#编程获取各种电脑硬件信息的方法,结合实例形式分析了c#获取电脑cpu、主板、硬盘、bios编号等信息的相关操作技巧与注意事项,需要的朋友可以参考下 本文实例讲述了C#编程获取各种电脑硬件信息的方法。分享给大家供大家参考…

    编程技术 2025年3月3日
    200
  • C#如何利用FileSystemWatcher控件实现的文件监控的具体示例分享

    这篇文章主要介绍了c#使用filesystemwatcher控件实现的文件监控功能,结合实例形式分析了c# filesystemwatcher组件的功能及监控文件更改情况的具体使用技巧,需要的朋友可以参考下 本文实例讲述了C#使用FileS…

    编程技术 2025年3月3日
    200
  • C#中关于foreach遍历使用的深入理解

    在c#中通过foreach遍历一个列表是经常拿用的方法,使用起来也方便,下面这篇文章先给大家介绍了关于c#中foreach遍历的使用方法,后面介绍了c#使用foreach注意的一些是,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习…

    2025年3月3日
    200
  • 从零讲解.Net Core开发实现图片文件上传下载

    这篇文章主要为大家详细介绍了.net core实现图片文件上传下载功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 当下.Net Core项目可是如雨后春笋一般发展起来,作为.Net大军中的一员,我热忱地拥抱了.Net Core并且积…

    2025年3月3日 编程技术
    200

发表回复

登录后才能评论