YbSoftwareFactory 代码生成插件【十五】:Show 一下最新的动态属性扩展功能与键值生成器功能...

介绍了YbSoftwareFactory中的动态属性扩展功能,允许灵活扩展属性,并自动处理属性类型的转换和序列化。此外,还详细讲解了键值生成器组件,支持格式化的键值生成,批量生成以及解决并发问题。

    YbSoftwareFactory 各种插件的基础类库中又新增了两个方便易用的功能:动态属性扩展与键值生成器,本章将分别介绍这两个非常方便的组件。

一、动态属性扩展 

    在实际的开发过程中,你肯定会遇到数据库字段不够用的情况,临时增加一个字段有时是很麻烦的一件事。例如需要修改 SQL 语句、视图、存储过程等等,即使你使用的是 ORM 组件,也需要增加和配置映射,每次修改完成后还需反复进行测试,非常的不方便,如果软件已经为客户部署好了的话,嘿嘿,不用说,肯定更让你头疼;而客户临时要添加新的字段的情况却是非常普遍的。另外,有些对象其实不适合放到一张主表中,即使这是 1:1 的关系,因为直接添加到一张表可能会存在一定的性能问题,例如图片、文件等信息,某些时候查询 N 多记录返回大量信息通常不是合理和明智的做法,在字段数量很多的情况下,对于某些不重要的字段信息保存到其他表中通常是可以提升查询性能的。

    本章介绍的动态属性扩展功能主要就是解决此类问题,可以灵活、方便的扩展属性。

    注:动态属性扩展组件主要面向正在开发中的审批流组件而设计的,其目的是为终端用户提供灵活、方便、易用的属性自定义的功能。动态属性扩展组件已集成到数据字典组件、组织机构管理组件中。 

    本组件具有如下显著特点:

  1. 自动完成动态属性值的加载和保存,通过键/值对的方式实现动态扩展属性的数据库保存和加载,非常的方便。如果你想玩得更高级点,可以直接从界面绑定一个动态属性,然后保存到数据库并能重新加载并绑定到界面上,这一过程无需你像某些软件类似的对所谓的元数据进行管理和配置,非常灵活。
  2. 能自动完成属性类型的转换,因为字段的属性值是通过键值对的方式保存到指定的数据库表中,因此需要把数据库中保存的文本型的属性值自动转换成指定的类型(如日期、整数、二进制信息)等。本文介绍的动态属性扩展功能可完成此类型的转换。
  3. 支持对象的序列化,这对于使用 WCF、Web Service、Web API 等类似的技术进行远程数据交互是很有必要的。 

    至于具体的实现原理,毫无疑问是利用了 .NET 4.0 的 Dynamic 特性,如下是核心基类的实现代码:

ExpandedBlockStart.gif
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Dynamic;
  5 using System.Reflection;
  6 
  7 namespace Yb.Data.Provider
  8 {
  9     [Serializable]
 10     public class ExtensionObject: DynamicObject, IDynamicMetaObjectProvider
 11     {
 12         object _instance;
 13 
 14         Type _instanceType;
 15         PropertyInfo[] _cacheInstancePropertyInfos;
 16         IEnumerable<PropertyInfo> _instancePropertyInfos
 17         {
 18             get
 19             {
 20                 if (_cacheInstancePropertyInfos == null && _instance != null)                
 21                     _cacheInstancePropertyInfos = _instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
 22                 return _cacheInstancePropertyInfos;                
 23             }
 24         }
 25 
 26         public ExtensionObject() 
 27         {
 28             Initialize(this);            
 29         }
 30 
 31         /// <remarks>
 32         /// You can pass in null here if you don't want to 
 33         /// check native properties and only check the Dictionary!
 34         /// </remarks>
 35         /// <param name="instance"></param>
 36         public ExtensionObject(object instance)
 37         {
 38             Initialize(instance);
 39         }
 40 
 41 
 42         protected virtual void Initialize(object instance)
 43         {
 44             _instance = instance;
 45             if (instance != null)
 46                 _instanceType = instance.GetType();           
 47         }
 48 
 49        /// <param name="binder"></param>
 50        /// <param name="result"></param>
 51        /// <returns></returns>
 52         public override bool TryGetMember(GetMemberBinder binder, out object result)
 53         {
 54             result = null;
 55 
 56             // first check the Properties collection for member
 57             if (Properties.Keys.Contains(binder.Name))
 58             {
 59                 result = Properties[binder.Name];
 60                 return true;
 61             }
 62 
 63 
 64             // Next check for Public properties via Reflection
 65             if (_instance != null)
 66             {
 67                 try
 68                 {
 69                     return GetProperty(_instance, binder.Name, out result);                    
 70                 }
 71                 catch (Exception)
 72                 { }
 73             }
 74 
 75             // failed to retrieve a property
 76             return false;
 77         }
 78 
 79         /// <param name="binder"></param>
 80         /// <param name="value"></param>
 81         /// <returns></returns>
 82         public override bool TrySetMember(SetMemberBinder binder, object value)
 83         {
 84 
 85             // first check to see if there's a native property to set
 86             if (_instance != null)
 87             {
 88                 try
 89                 {
 90                     bool result = SetProperty(_instance, binder.Name, value);
 91                     if (result)
 92                         return true;
 93                 }
 94                 catch { }
 95             }
 96             
 97             // no match - set or add to dictionary
 98             Properties[binder.Name] = value;
 99             return true;
100         }
101 
102         /// <param name="binder"></param>
103         /// <param name="args"></param>
104         /// <param name="result"></param>
105         /// <returns></returns>
106         public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
107         {
108             if (_instance != null)
109             {
110                 try
111                 {
112                     // check instance passed in for methods to invoke
113                     if (InvokeMethod(_instance, binder.Name, args, out result))
114                         return true;                    
115                 }
116                 catch (Exception)
117                 { }
118             }
119 
120             result = null;
121             return false;
122         }
123         
124         /// <param name="instance"></param>
125         /// <param name="name"></param>
126         /// <param name="result"></param>
127         /// <returns></returns>
128         protected bool GetProperty(object instance, string name, out object result)
129         {
130             if (instance == null)
131                 instance = this;
132 
133             var miArray = _instanceType.GetMember(name, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance);
134             if (miArray != null && miArray.Length > 0)
135             {
136                 var mi = miArray[0];
137                 if (mi.MemberType == MemberTypes.Property)
138                 {
139                     result = ((PropertyInfo)mi).GetValue(instance,null);
140                     return true;
141                 }
142             }
143 
144             result = null;
145             return false;                
146         }
147 
148         /// <param name="instance"></param>
149         /// <param name="name"></param>
150         /// <param name="value"></param>
151         /// <returns></returns>
152         protected bool SetProperty(object instance, string name, object value)
153         {
154             if (instance == null)
155                 instance = this;
156 
157             var miArray = _instanceType.GetMember(name, BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance);
158             if (miArray != null && miArray.Length > 0)
159             {
160                 var mi = miArray[0];
161                 if (mi.MemberType == MemberTypes.Property)
162                 {
163                     ((PropertyInfo)mi).SetValue(_instance, value, null);
164                     return true;
165                 }
166             }
167             return false;                
168         }
169 
170         /// <param name="instance"></param>
171         /// <param name="name"></param>
172         /// <param name="args"></param>
173         /// <param name="result"></param>
174         /// <returns></returns>
175         protected bool InvokeMethod(object instance, string name, object[] args, out object result)
176         {
177             if (instance == null)
178                 instance = this;
179 
180             // Look at the instanceType
181             var miArray = _instanceType.GetMember(name,
182                                     BindingFlags.InvokeMethod |
183                                     BindingFlags.Public | BindingFlags.Instance);
184 
185             if (miArray != null && miArray.Length > 0)
186             {
187                 var mi = miArray[0as MethodInfo;
188                 result = mi.Invoke(_instance, args);
189                 return true;
190             }
191 
192             result = null;
193             return false;
194         }
195 
196         public object this[string key]
197         {
198             get
199             {
200                 try
201                 {
202                     // try to get from properties collection first
203                     return Properties[key];
204                 }
205                 catch (KeyNotFoundException ex)
206                 {
207                     // try reflection on instanceType
208                     object result = null;
209                     if (GetProperty(_instance, key, out result))
210                         return result;
211 
212                     // nope doesn't exist
213                     throw;
214                 }
215             }
216             set
217             {
218                 if (Properties.ContainsKey(key))
219                 {
220                     Properties[key] = value;
221                     return;
222                 }
223 
224                 // check instance for existance of type first
225                 var miArray = _instanceType.GetMember(key, BindingFlags.Public | BindingFlags.GetProperty);
226                 if (miArray != null && miArray.Length > 0)
227                     SetProperty(_instance, key, value);
228                 else
229                     Properties[key] = value;
230             }
231         }
232 
233         /// <param name="includeInstanceProperties"></param>
234         /// <returns></returns>
235         public IEnumerable<KeyValuePair<string,object>> GetProperties(bool includeInstanceProperties = false)
236         {
237             if (includeInstanceProperties && _instance != null)
238             {
239                 foreach (var prop in this._instancePropertyInfos)
240                     yield return new KeyValuePair<stringobject>(prop.Name, prop.GetValue(_instance, null));
241             }
242 
243             foreach (var key in this.Properties.Keys)
244                yield return new KeyValuePair<stringobject>(key, this.Properties[key]);
245 
246         }
247 
248         /// <param name="item"></param>
249         /// <param name="includeInstanceProperties"></param>
250         /// <returns></returns>
251         public bool Contains(KeyValuePair<stringobject> item, bool includeInstanceProperties = false)
252         {
253             bool res = Properties.ContainsKey(item.Key);
254             if (res)
255                 return true;
256 
257             if (includeInstanceProperties && _instance != null)
258             {
259                 foreach (var prop in this._instancePropertyInfos)
260                 {
261                     if (prop.Name == item.Key)
262                         return true;
263                 }
264             }
265 
266             return false;
267         }
268         /// <param name="key"></param>
269         /// <returns></returns>
270         public bool Contains(string key, bool includeInstanceProperties = false)
271         {
272             bool res = Properties.ContainsKey(key);
273             if (res)
274                 return true;
275 
276             if (includeInstanceProperties && _instance != null)
277             {
278                 foreach (var prop in this._instancePropertyInfos)
279                 {
280                     if (prop.Name == key)
281                         return true;
282                 }
283             }
284 
285             return false;
286         }
287         
288     }
289 }
ExtensionObject

    具体的使用,仅需继承该对象即可。为了更好的说明具体用法,请查看如下已测试通过的单元测试代码:

ExpandedBlockStart.gif
  1         [Serializable]
  2         public class User : ExtensionObject
  3         {
  4             public Guid UserId { getset; }
  5             public string Email { getset; }
  6             public string Password { getset; }
  7             public string Name { getset; }
  8             public bool Active { getset; }
  9             public DateTime? ExpiresOn { getset; }
 10 
 11             public User()
 12                 : base()
 13             { }
 14 
 15             // only required if you want to mix in seperate instance
 16             public User(object instance)
 17                 : base(instance)
 18             {
 19             }
 20         }
 21 
 22         /// <summary>
 23         /// ExtensionData 的测试
 24         ///</summary>
 25         [TestMethod()]
 26         public void ExtensionObjectTest()
 27         {
 28             //清空数据库存储的属性值,方便进行测试
 29             ExtensionDataApi.ClearExtensionDataOfApplication();
 30 
 31             var user = new User();
 32             // 设置已有属性
 33             dynamic duser = user;
 34             user.UserId = Guid.NewGuid();
 35             duser.Email = "19892257@qq.com";
 36             user.Password = "YbSofteareFactory";
 37 
 38             // 设置动态属性
 39             duser.FriendUserName = "YB";
 40             duser.CreatedDate = DateTime.Now;
 41             duser.TodayNewsCount = 1;
 42             duser.Age = 27.5;
 43             duser.LastUpdateId = (Guid?)null;
 44             duser.LastUpdatedDate=null;
 45 
 46             // 动态属性值保存
 47             ExtensionDataApi.SaveExtensionObject(user.UserId,user);
 48             
 49             // 从数据库中加载属性值
 50             var obj = user.LoadExtensionData(user.UserId);
 51             
 52             // 测试是否加载正确
 53             Assert.AreEqual(obj.FriendUserName, "YB");
 54             Assert.IsNotNull(obj.CreatedDate);
 55             Assert.AreEqual(obj.TodayNewsCount, 1);
 56             Assert.AreEqual(obj.Age, 27.5);
 57             Assert.IsNull(obj.LastUpdateId);
 58             Assert.IsNull(obj.LastUpdatedDate);
 59 
 60             var items = ExtensionDataApi.FindExtensionDataBy(user.UserId.ToString(), user);
 61             //测试保存的动态属性数
 62             Assert.IsTrue(items.Count() == 6);
 63 
 64             // 修改动态属性值
 65             duser.Age = 28;
 66             // 新增动态属性
 67             duser.Tag = null;
 68             duser.NewProperty = 12;
 69             //使用扩展方法进行保存动态属性值至数据库
 70             user.SaveExtensionData(user.UserId);
 71             
 72             items = ExtensionDataApi.FindExtensionDataBy(user.UserId.ToString(), user);
 73             //判断保存的属性数量是否正确
 74             Assert.IsTrue(items.Count() == 8);
 75 
 76             //使用扩展方法动态从数据库中加载属性
 77             obj = user.LoadExtensionData(user.UserId);
 78 
 79             Assert.AreEqual(obj.Tag, null);
 80             Assert.AreEqual(obj.NewProperty, 12);
 81 
 82             duser.ComplexObject = user;
 83 
 84             //设置新值
 85             duser.Tag = true;
 86             ExtensionDataApi.SaveExtensionObject(user.UserId, user);
 87             obj = ExtensionDataApi.LoadExtensionObject(user.UserId, user);
 88             // 验证加载的属性新值是否正确
 89             Assert.IsTrue(obj.Tag);
 90 
 91             //返回对象数组的属性字典方法测试
 92             var dic = ExtensionDataApi.FindExtensionDataDictionaryBy(new string[]{user.UserId.ToString()}, user.GetType().FullName);
 93             Assert.IsTrue(dic.Count>0);
 94 
 95             //byte[] 测试,对可方便存储文件、图片等内容
 96             duser.Image = new byte[] {225524123616191283290};
 97             ExtensionDataApi.SaveExtensionObject(user.UserId, user);
 98             obj = ExtensionDataApi.LoadExtensionObject(user.UserId, user);
 99             Assert.AreEqual(obj.Image.Length, 9);
100             Assert.AreEqual(obj.Image[8], 90);
101 
102             //Json 序列化测试,对 Web Api 等非常重要
103             string json = JsonConvert.SerializeObject(duser, Formatting.Indented, new JsonSerializerSettings
104             {
105                 TypeNameHandling = TypeNameHandling.All,
106                 TypeNameAssemblyFormat = FormatterAssemblyStyle.Full
107             });
108             Assert.IsNotNull(json);
109             json = JsonConvert.SerializeObject(user);
110             Assert.IsNotNull(json);
111         }
ExtensionObjectTest

二、键值生成器

    键值的生成看似简单,其实实现起来却并不容易,因为这里面有并发性、生成效率等等方面的考虑。同时,对键值的管理也是非常重要的,试想想,不同位置的两个客户端同时生成了相同的键值是什么后果吧。

    本章要介绍的键值生成器组件非常灵活和高效,它具有如下非常实用的功能:

  1. 支持绝大多数情况下指定格式的键值生成,例如可指定前缀、后缀、客户端应用程序编号(多客户端下非常有用)、日期(例如yyyy、yyyyMM、yyyyMMdd、yyyyMMddHH等)以及流水号长度。
  2. 支持批量生成键值,一次可以生成指定数量的键值组。
  3. 在满足特定性能的前提下,可有效解决常见的并发情况,有效防止键值冲突。

   对于具体的使用方式,同样还是来看看已通过测试的部分单元测试代码:

ExpandedBlockStart.gif
  1         /// <summary>
  2         ///GetNextID 的测试
  3         ///</summary>
  4         [TestMethod()]
  5         public void GetNextIDTest()
  6         {
  7             IdGeneratorApi.ClearAllIdGenerator();
  8 
  9             var user = new User();
 10 
 11             //生成类似 U-01-201308-001格式的ID,%A表示输出客户端编号,%D表示输出日期时间
 12             var idGen = new IdGenerator()
 13                 {
 14                     Type = typeof (User).FullName,
 15                     DateFormat = "yyyyMM",
 16                     GenFormat = "U-%A-%D-",
 17                     Id = Guid.NewGuid(),
 18                     StartValue = 1,
 19                     NextValue = 1,
 20                     ValueLength = 3
 21                 };
 22             //API基本方法测试
 23             IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
 24             var item = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
 25             Assert.IsNotNull(item);
 26             item = IdGeneratorApi.GetIdGeneratorBy(user);
 27             Assert.IsNotNull(item);
 28             item = IdGeneratorApi.GetIdGeneratorBy("not exist's record");
 29             Assert.IsNull(item);
 30             //API基本方法测试
 31             Assert.IsTrue(IdGeneratorApi.IdGeneratorExists(user));
 32             Assert.IsFalse(IdGeneratorApi.IdGeneratorExists("dkakd_test_a"));
 33 
 34             //生成ID号
 35             var str = IdGeneratorApi.GetNextID(user);
 36             Assert.AreEqual("U-02-201308-001", str);
 37             str = IdGeneratorApi.GetNextID(user);
 38             Assert.AreEqual("U-02-201308-002", str);
 39 
 40             idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
 41             //无需生成日期,当前生成的ID号类似于U-02--003
 42             idGen.DateFormat = string.Empty;
 43 
 44             IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
 45             idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
 46 
 47             //生成下一ID号
 48             str = IdGeneratorApi.GetNextID(user);
 49             Assert.AreEqual("U-02--003", str);
 50             str = IdGeneratorApi.GetNextID(user);
 51             Assert.AreEqual("U-02--004", str);
 52 
 53             idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
 54             // 如下代码修改生成的ID号类似于U-0005-
 55             idGen.DateFormat = "yyyyMM";
 56             //未设置%D,将不再输出日期
 57             idGen.GenFormat = "U-%v-";
 58             //修改生成编号的长度为4
 59             idGen.ValueLength = 4
 60             IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
 61 
 62             str = IdGeneratorApi.GetNextID(user);
 63             Assert.AreEqual("U-0005-", str);
 64             str = IdGeneratorApi.GetNextID(user);
 65             Assert.AreEqual("U-0006-", str);
 66 
 67             //API基本方法测试
 68             IdGeneratorApi.DeleteIdGenerator(idGen);
 69             item = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
 70             Assert.IsNull(item);
 71             item = IdGeneratorApi.GetIdGeneratorBy(user);
 72             Assert.IsNull(item);
 73 
 74             IdGeneratorApi.ClearAllIdGeneratorOfApplication();
 75         }
 76 
 77         /// <summary>
 78         ///GetNextGroupID 的测试,批量生产ID号
 79         ///</summary>
 80         [TestMethod()]
 81         public void GetNextGroupIDTest()
 82         {
 83             IdGeneratorApi.ClearAllIdGeneratorOfApplication();
 84 
 85             var user = new User();
 86 
 87             var idGen = new IdGenerator()
 88             {
 89                 Type = typeof(User).FullName,
 90                 DateFormat = "yyyyMM",
 91                 GenFormat = "U-%a-%D-%v",
 92                 Id = Guid.NewGuid(),
 93                 StartValue = 1,
 94                 NextValue = 1,
 95                 ValueLength = 3
 96             };
 97 
 98             IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
 99 
100             //批量生成3个ID号
101             var str = IdGeneratorApi.GetNextGroupID(user,3);
102             Assert.IsTrue(str.Length==3);
103             Assert.IsTrue(str[0]=="U-02-201308-001");
104             Assert.IsTrue(str[1]=="U-02-201308-002");
105             Assert.IsTrue(str[2]=="U-02-201308-003");
106 
107             idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
108             // 如下修改将生成类似于T0004的ID,将忽略日期和客户端编号
109             idGen.GenFormat = "T%v";
110             idGen.ValueLength = 4;
111             IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
112 
113             str = IdGeneratorApi.GetNextGroupID(user,2);
114             Assert.IsTrue(str.Length==2);
115             Assert.IsTrue(str[0]=="T0004");
116             Assert.IsTrue(str[1]=="T0005");
117 
118             idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
119             //修改生成的ID格式
120             idGen.DateFormat = "yyyy";
121             //生成类似于01-0010/2013的ID号,%a为客户端编号,%v为流水号,%d将输出日期时间,此处为年份
122             idGen.GenFormat = "%a-%v/%d";
123             //指明流水号长度为4,类似于0001
124             idGen.ValueLength = 4;
125             IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
126 
127             str = IdGeneratorApi.GetNextGroupID(user,2);
128             Assert.IsTrue(str.Length==2);
129             Assert.IsTrue(str[0]=="02-0001/2013");
130             Assert.IsTrue(str[1]=="02-0002/2013");
131 
132             IdGeneratorApi.ClearAllIdGenerator();
133         }
134 
135         public class User
136         {
137             public string Id { getset; }
138             public string UserName { getset; }
139         }
IdGeneratorTest

   目前的开发重心将逐渐向审批流的开发过渡,未来的审批流组件将由表单设计器、流程设计器和审批流底层组件三大部分组成,具有灵活、简单、易用的特点,如下是流程设计器的预览界面:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值