YbSoftwareFactory 代码生成插件【十五】:Show 一下最新的动态属性扩展功能与键值生成器功能
YbSoftwareFactory 各种插件的基础类库中又新增了两个方便易用的功能:动态属性扩展与键值生成器,本章将分别介绍这两个非常方便的组件。
一、动态属性扩展
在实际的开发过程中,你肯定会遇到数据库字段不够用的情况,临时增加一个字段有时是很麻烦的一件事。例如需要修改 SQL 语句、视图、存储过程等等,即使你使用的是 ORM 组件,也需要增加和配置映射,每次修改完成后还需反复进行测试,非常的不方便,如果软件已经为客户部署好了的话,嘿嘿,不用说,肯定更让你头疼;而客户临时要添加新的字段的情况却是非常普遍的。另外,有些对象其实不适合放到一张主表中,即使这是 1:1 的关系,因为直接添加到一张表可能会存在一定的性能问题,例如图片、文件等信息,某些时候查询 N 多记录返回大量信息通常不是合理和明智的做法,在字段数量很多的情况下,对于某些不重要的字段信息保存到其他表中通常是可以提升查询性能的。
本章介绍的动态属性扩展功能主要就是解决此类问题,可以灵活、方便的扩展属性。
注:动态属性扩展组件主要面向正在开发中的审批流组件而设计的,其目的是为终端用户提供灵活、方便、易用的属性自定义的功能。动态属性扩展组件已集成到数据字典组件、组织机构管理组件中。
本组件具有如下显著特点:
- 自动完成动态属性值的加载和保存,通过键/值对的方式实现动态扩展属性的数据库保存和加载,非常的方便。如果你想玩得更高级点,可以直接从界面绑定一个动态属性,然后保存到数据库并能重新加载并绑定到界面上,这一过程无需你像某些软件类似的对所谓的元数据进行管理和配置,非常灵活。
- 能自动完成属性类型的转换,因为字段的属性值是通过键值对的方式保存到指定的数据库表中,因此需要把数据库中保存的文本型的属性值自动转换成指定的类型(如日期、整数、二进制信息)等。本文介绍的动态属性扩展功能可完成此类型的转换。
- 支持对象的序列化,这对于使用 WCF、Web Service、Web API 等类似的技术进行远程数据交互是很有必要的。
至于具体的实现原理,毫无疑问是利用了 .NET 4.0 的 Dynamic 特性,如下是核心基类的实现代码:
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;
}
/// <param name="binder"></param>
/// <param name="args"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
if (_instance != null)
{
try
{
// check instance passed in for methods to invoke
if (InvokeMethod(_instance, binder.Name, args, out result))
return true;
}
catch (Exception)
{ }
}
result = null;
return false;
}
/// <param name="instance"></param>
/// <param name="name"></param>
/// <param name="result"></param>
/// <returns></returns>
protected bool GetProperty(object instance, string name, out object result)
{
if (instance == null)
instance = this;
var miArray = _instanceType.GetMember(name, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance);
)
{
];
if (mi.MemberType == MemberTypes.Property)
{
result = ((PropertyInfo)mi).GetValue(instance,null);
return true;
}
}
result = null;
return false;
}
/// <param name="instance"></param>
/// <param name="name"></param>
/// <param name="value"></param>
/// <returns></returns>
protected bool SetProperty(object instance, string name, object value)
{
if (instance == null)
instance = this;
var miArray = _instanceType.GetMember(name, BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance);
)
{
];
if (mi.MemberType == MemberTypes.Property)
{
((PropertyInfo)mi).SetValue(_instance, value, null);
return true;
}
}
return false;
}
/// <param name="instance"></param>
/// <param name="name"></param>
/// <param name="args"></param>
/// <param name="result"></param>
/// <returns></returns>
protected bool InvokeMethod(object instance, string name, object[] args, out object result)
{
if (instance == null)
instance = this;
// Look at the instanceType
var miArray = _instanceType.GetMember(name,
BindingFlags.InvokeMethod |
BindingFlags.Public | BindingFlags.Instance);
)
{
] as MethodInfo;
result = mi.Invoke(_instance, args);
return true;
}
result = null;
return false;
}
public object this[string key]
{
get
{
try
{
// try to get from properties collection first
return Properties[key];
}
catch (KeyNotFoundException ex)
{
// try reflection on instanceType
object result = null;
if (GetProperty(_instance, key, out result))
return result;
// nope doesn't exist
throw;
}
}
set
{
if (Properties.ContainsKey(key))
{
Properties[key] = value;
return;
}
// check instance for existance of type first
var miArray = _instanceType.GetMember(key, BindingFlags.Public | BindingFlags.GetProperty);
)
SetProperty(_instance, key, value);
else
Properties[key] = value;
}
}
/// <param name="includeInstanceProperties"></param>
/// <returns></returns>
public IEnumerable<KeyValuePair<string,object>> GetProperties(bool includeInstanceProperties = false)
{
if (includeInstanceProperties && _instance != null)
{
foreach (var prop in this._instancePropertyInfos)
yield return new KeyValuePair<string, object>(prop.Name, prop.GetValue(_instance, null));
}
foreach (var key in this.Properties.Keys)
yield return new KeyValuePair<string, object>(key, this.Properties[key]);
}
/// <param name="item"></param>
/// <param name="includeInstanceProperties"></param>
/// <returns></returns>
public bool Contains(KeyValuePair<string, object> item, bool includeInstanceProperties = false)
{
bool res = Properties.ContainsKey(item.Key);
if (res)
return true;
if (includeInstanceProperties && _instance != null)
{
foreach (var prop in this._instancePropertyInfos)
{
if (prop.Name == item.Key)
return true;
}
}
return false;
}
/// <param name="key"></param>
/// <returns></returns>
public bool Contains(string key, bool includeInstanceProperties = false)
{
bool res = Properties.ContainsKey(key);
if (res)
return true;
if (includeInstanceProperties && _instance != null)
{
foreach (var prop in this._instancePropertyInfos)
{
if (prop.Name == key)
return true;
}
}
return false;
}
}
}
ExtensionObject
具体的使用,仅需继承该对象即可。为了更好的说明具体用法,请查看如下已测试通过的单元测试代码:
2 public class User : ExtensionObject
3 {
4 public Guid UserId { get; set; }
5 public string Email { get; set; }
6 public string Password { get; set; }
7 public string Name { get; set; }
8 public bool Active { get; set; }
9 public DateTime? ExpiresOn { get; set; }
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;
;
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);
);
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 //测试保存的动态属性数
);
63
64 // 修改动态属性值
;
66 // 新增动态属性
67 duser.Tag = null;
;
69 //使用扩展方法进行保存动态属性值至数据库
70 user.SaveExtensionData(user.UserId);
71
72 items = ExtensionDataApi.FindExtensionDataBy(user.UserId.ToString(), user);
73 //判断保存的属性数量是否正确
);
75
76 //使用扩展方法动态从数据库中加载属性
77 obj = user.LoadExtensionData(user.UserId);
78
79 Assert.AreEqual(obj.Tag, null);
);
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);
);
94
95 //byte[] 测试,对可方便存储文件、图片等内容
, , , , , , , , };
97 ExtensionDataApi.SaveExtensionObject(user.UserId, user);
98 obj = ExtensionDataApi.LoadExtensionObject(user.UserId, user);
);
Assert.AreEqual(obj.Image[], );
//Json 序列化测试,对 Web Api 等非常重要
string json = JsonConvert.SerializeObject(duser, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
TypeNameAssemblyFormat = FormatterAssemblyStyle.Full
});
Assert.IsNotNull(json);
json = JsonConvert.SerializeObject(user);
Assert.IsNotNull(json);
}
ExtensionObjectTest
二、键值生成器
键值的生成看似简单,其实实现起来却并不容易,因为这里面有并发性、生成效率等等方面的考虑。同时,对键值的管理也是非常重要的,试想想,不同位置的两个客户端同时生成了相同的键值是什么后果吧。
本章要介绍的键值生成器组件非常灵活和高效,它具有如下非常实用的功能:
- 支持绝大多数情况下指定格式的键值生成,例如可指定前缀、后缀、客户端应用程序编号(多客户端下非常有用)、日期(例如yyyy、yyyyMM、yyyyMMdd、yyyyMMddHH等)以及流水号长度。
- 支持批量生成键值,一次可以生成指定数量的键值组。
- 在满足特定性能的前提下,可有效解决常见的并发情况,有效防止键值冲突。
对于具体的使用方式,同样还是来看看已通过测试的部分单元测试代码:
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(),
,
,
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
;
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(),
,
,
96 };
97
98 IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
99
//批量生成3个ID号
);
Assert.IsTrue(str.Length==);
Assert.IsTrue(str[]=="U-02-201308-001");
Assert.IsTrue(str[]=="U-02-201308-002");
Assert.IsTrue(str[]=="U-02-201308-003");
idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
// 如下修改将生成类似于T0004的ID,将忽略日期和客户端编号
idGen.GenFormat = "T%v";
idGen.ValueLength = ;
IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
str = IdGeneratorApi.GetNextGroupID(user,);
Assert.IsTrue(str.Length==);
Assert.IsTrue(str[]=="T0004");
Assert.IsTrue(str[]=="T0005");
idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
//修改生成的ID格式
idGen.DateFormat = "yyyy";
//生成类似于01-0010/2013的ID号,%a为客户端编号,%v为流水号,%d将输出日期时间,此处为年份
idGen.GenFormat = "%a-%v/%d";
//指明流水号长度为4,类似于0001
;
IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
str = IdGeneratorApi.GetNextGroupID(user,);
Assert.IsTrue(str.Length==);
Assert.IsTrue(str[]=="02-0001/2013");
Assert.IsTrue(str[]=="02-0002/2013");
IdGeneratorApi.ClearAllIdGenerator();
}
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
}
IdGeneratorTest
目前的开发重心将逐渐向审批流的开发过渡,未来的审批流组件将由表单设计器、流程设计器和审批流底层组件三大部分组成,具有灵活、简单、易用的特点,如下是流程设计器的预览界面:
YbSoftwareFactory 代码生成插件【十五】:Show 一下最新的动态属性扩展功能与键值生成器功能的更多相关文章
- Show 一下最新的动态属性扩展功能与键值生成器功能
Show 一下最新的动态属性扩展功能与键值生成器功能 YbSoftwareFactory 各种插件的基础类库中又新增了两个方便易用的功能:动态属性扩展与键值生成器,本章将分别介绍这两个非常方便的组件. ...
- YbSoftwareFactory 代码生成插件【十四】:通过 DynamicLinq 简单实现 N-Tier 部署下的服务端数据库通用分页
YbSoftwareFactory 的 YbRapidSolution for WinForm 插件使用CSLA.NET作为业务层,CSLA.NET的一个强大的特性是支持 N-Tiers 部署.只需非 ...
- YbSoftwareFactory 代码生成插件【二十五】:Razor视图中以全局方式调用后台方法输出页面代码的三种方法
上一篇介绍了 MVC中实现动态自定义路由 的实现,本篇将介绍Razor视图中以全局方式调用后台方法输出页面代码的三种方法. 框架最新的升级实现了一个页面部件功能,其实就是通过后台方法查询数据库内容,把 ...
- YbSoftwareFactory 代码生成插件【十九】:实体类配合数据库表字段进行属性扩展的小技巧
实体类通常需要和数据库表进行了ORM映射,当你需要添加新的属性时,往往同时也需要在数据库中添加相应的字段并配置好映射关系,同时可能还需对数据访问组件进行重新编译和部署才能有效.而当你开始设计一个通用数 ...
- YbSoftwareFactory 代码生成插件【二十】:DynamicObject的序列化
DynamicObject 是 .NET 4.0以来才支持的一个类,但该类在.NET 4.0下未被标记为[Serializable] Attribute,而在.NET 4.5下则被标记了[Serial ...
- YbSoftwareFactory 代码生成插件【十八】:树形结构下的查询排序的数据库设计
树形结构的排序在中国特色下十分普遍也非常重要,例如常说的五大班子,党委>人大>政府>政协>纪委,每个班子下还有部门,岗位,人员,最终排列的顺序通常需要按权力大小.重要性等进行排 ...
- YbSoftwareFactory 代码生成插件【十六】:Web 下灵活、强大的审批流程实现(含流程控制组件、流程设计器和表单设计器)
程序=数据结构+算法,而企业级的软件=数据+流程,流程往往千差万别,客户自身有时都搞不清楚,随时变化的情况更是家常便饭,抛开功能等不谈,需求变化很大程度上就是流程的变化,流程的变化会给开发工作造成很大 ...
- YbSoftwareFactory 代码生成插件【二十四】:MVC中实现动态自定义路由
上一篇介绍了 公文流转系统 的实现,本篇介绍下MVC下动态自定义路由的实现. 在典型的CMS系统中,通常需要为某个栏目指定个友链地址,通过指定友链地址,该栏目的地址更人性化.方便记忆,也有利用于搜索引 ...
- YbSoftwareFactory 代码生成插件【二十二】:CMS基础功能的实现
很多网友建议在YbRapidSolution for MVC框架的基础上实现CMS功能,以方便进行内容的管理,加快前端页面的开发速度.因此花了一段时间,实现了一套CMS内容发布系统并已集成至YbRap ...
随机推荐
- JavaScript之数组方法整理
Array概述 除了Object类型,最常用的类型: 实质:有序的数据列表, 特性:可以动态的调整数组的大小 创建数组的两种方式 构造函数创建方式 var arr = ...
- Java Mybatis 框架入门教程
一.Mybatis介绍 MyBatis是一款一流的支持自定义SQL.存储过程和高级映射的持久化框架.MyBatis几乎消除了所有的JDBC代码,也基本不需要手工去 设置参数和获取检索结果.MyBati ...
- linux kernel链表
参考: http://blog.csdn.net/echoisland/article/details/7079943
- JMeter 将上一个请求的结果作为下一个请求的参数——使用正则提取器(转载)
在接口测试和压力测试过程中,经常会将几个流程串联起来才能测试.如:我要进行获取用户信息接口测试,我就要先登录成功后,才能获取用户信息.所以,我就要首先要登录,获得我的登录凭证(tokenId或tick ...
- php-fpm重启关闭等操作
php-fpm 启动:/usr/sbin/php-fpmphp-fpm 关闭:kill -INT `cat /var/run/php-fpm.pid`php-fpm 重启:kill -USR2 `ca ...
- 几何服务,cut功能测试
关于几何服务 几何服务用于辅助应用程序执行各种几何计算,如缓冲区.简化.面积和长度计算以及投影.在 ArcGIS Server 管理器中启动几何服务之后,您才能够在应用程序开发过程中使用该服务. 问题 ...
- selenium python的使用(一)
下面是一个爬取知网数据的例子,使用selenium 用python爬取数据 1.创建对象,打开指定地址,在休眠的20秒内输入搜索项 driver= webdriver.Chrome() driver. ...
- SignalR 远程访问并跨域
http://stackoverflow.com/questions/16875228/how-do-i-get-a-signalr-hub-connection-to-work-cross-doma ...
- 一次性搞明白 service和factory区别
原文链接 http://blog.thoughtram.io/angular/2015/07/07/service-vs-factory-once-and-for-all.html 等下,已经有一篇文 ...
- SpringMVC常用配置-Controller返回格式化数据如JSON、XML的配置方式和机制