YbSoftwareFactory 代碼生成插件【十五】:Show 一下最新的動態屬性擴展功能與鍵值生成器功能

    YbSoftwareFactory 各類插件的基礎類庫中又新增了兩個方便易用的功能:動態屬性擴展與鍵值生成器,本章將分別介紹這兩個很是方便的組件。數據庫

1、動態屬性擴展 json

    在實際的開發過程當中,你確定會遇到數據庫字段不夠用的狀況,臨時增長一個字段有時是很麻煩的一件事。例如須要修改 SQL 語句、視圖、存儲過程等等,即便你使用的是 ORM 組件,也須要增長和配置映射,每次修改完成後還需反覆進行測試,很是的不方便,若是軟件已經爲客戶部署好了的話,嘿嘿,不用說,確定更讓你頭疼;而客戶臨時要添加新的字段的狀況倒是很是廣泛的。另外,有些對象其實不適合放到一張主表中,即便這是 1:1 的關係,由於直接添加到一張表可能會存在必定的性能問題,例如圖片、文件等信息,某些時候查詢 N 多記錄返回大量信息一般不是合理和明智的作法,在字段數量不少的狀況下,對於某些不重要的字段信息保存到其餘表中一般是能夠提高查詢性能的。數組

    本章介紹的動態屬性擴展功能主要就是解決此類問題,能夠靈活、方便的擴展屬性。併發

    注:動態屬性擴展組件主要面向正在開發中的審批流組件而設計的,其目的是爲終端用戶提供靈活、方便、易用的屬性自定義的功能。動態屬性擴展組件已集成到數據字典組件、組織機構管理組件中。 ide

    本組件具備以下顯著特色:性能

  1. 自動完成動態屬性值的加載和保存,經過鍵/值對的方式實現動態擴展屬性的數據庫保存和加載,很是的方便。若是你想玩得更高級點,能夠直接從界面綁定一個動態屬性,而後保存到數據庫並能從新加載並綁定到界面上,這一過程無需你像某些軟件相似的對所謂的元數據進行管理和配置,很是靈活。
  2. 能自動完成屬性類型的轉換,由於字段的屬性值是經過鍵值對的方式保存到指定的數據庫表中,所以須要把數據庫中保存的文本型的屬性值自動轉換成指定的類型(如日期、整數、二進制信息)等。本文介紹的動態屬性擴展功能可完成此類型的轉換。
  3. 支持對象的序列化,這對於使用 WCF、Web Service、Web API 等相似的技術進行遠程數據交互是頗有必要的。 

    至於具體的實現原理,毫無疑問是利用了 .NET 4.0 的 Dynamic 特性,以下是核心基類的實現代碼:單元測試

  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

    具體的使用,僅需繼承該對象便可。爲了更好的說明具體用法,請查看以下已測試經過的單元測試代碼:測試

  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

2、鍵值生成器ui

    鍵值的生成看似簡單,其實實現起來卻並不容易,由於這裏面有併發性、生成效率等等方面的考慮。同時,對鍵值的管理也是很是重要的,試想一想,不一樣位置的兩個客戶端同時生成了相同的鍵值是什麼後果吧。this

    本章要介紹的鍵值生成器組件很是靈活和高效,它具備以下很是實用的功能:

  1. 支持絕大多數狀況下指定格式的鍵值生成,例如可指定前綴、後綴、客戶端應用程序編號(多客戶端下很是有用)、日期(例如yyyy、yyyyMM、yyyyMMdd、yyyyMMddHH等)以及流水號長度。
  2. 支持批量生成鍵值,一次能夠生成指定數量的鍵值組。
  3. 在知足特定性能的前提下,可有效解決常見的併發狀況,有效防止鍵值衝突。

   對於具體的使用方式,一樣仍是來看看已經過測試的部分單元測試代碼:

  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

   目前的開發重心將逐漸向審批流的開發過渡,將來的審批流組件將由表單設計器、流程設計器和審批流底層組件三大部分組成,具備靈活、簡單、易用的特色,以下是流程設計器的預覽界面:

 

相關文章
相關標籤/搜索