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#中怎样监控类属性更改的代码案例分享的细致内容,更多请关注ki4网别的相干文章!