Show 一下最新的动态属性扩展功能与键值生成器功能

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

一、动态属性扩展

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

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

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

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

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

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

 ExtensionObject
using System;
using System.Collections.Generic;
using System.Linq;
using System.Dynamic;
using System.Reflection;
namespace Yb.Data.Provider
{
[Serializable]
public class ExtensionObject: DynamicObject, IDynamicMetaObjectProvider
{
object _instance;
Type _instanceType;
PropertyInfo[] _cacheInstancePropertyInfos;
IEnumerable<PropertyInfo> _instancePropertyInfos
{
get
{
if (_cacheInstancePropertyInfos == null && _instance != null)
_cacheInstancePropertyInfos = _instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
return _cacheInstancePropertyInfos;
}
}
public ExtensionObject()
{
Initialize(this);
}
/// <remarks>
/// You can pass in null here if you don't want to
/// check native properties and only check the Dictionary!
/// </remarks>
/// <param name="instance"></param>
public ExtensionObject(object instance)
{
Initialize(instance);
} protected virtual void Initialize(object instance)
{
_instance = instance;
if (instance != null)
_instanceType = instance.GetType();
}
/// <param name="binder"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
// first check the Properties collection for member
if (Properties.Keys.Contains(binder.Name))
{
result = Properties[binder.Name];
return true;
} // Next check for Public properties via Reflection
if (_instance != null)
{
try
{
return GetProperty(_instance, binder.Name, out result);
}
catch (Exception)
{ }
}
// failed to retrieve a property
return false;
}
/// <param name="binder"></param>
/// <param name="value"></param>
/// <returns></returns>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
// first check to see if there's a native property to set
if (_instance != null)
{
try
{
bool result = SetProperty(_instance, binder.Name, value);
if (result)
return true;
}
catch { }
} // no match - set or add to dictionary
Properties[binder.Name] = value;
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 (miArray != null && miArray.Length > 0)
{
var mi = miArray[0];
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 (miArray != null && miArray.Length > 0)
{
var mi = miArray[0];
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);
if (miArray != null && miArray.Length > 0)
{
var mi = miArray[0] 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);
if (miArray != null && miArray.Length > 0)
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

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

 ExtensionObjectTest
[Serializable]
public class User : ExtensionObject
{
public Guid UserId { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public DateTime? ExpiresOn { get; set; }
public User()
: base()
{ }
// only required if you want to mix in seperate instance
public User(object instance)
: base(instance)
{
}
}
/// <summary>
/// ExtensionData 的测试
///</summary>
[TestMethod()]
public void ExtensionObjectTest()
{
//清空数据库存储的属性值,方便进行测试
ExtensionDataApi.ClearExtensionDataOfApplication();
var user = new User();
// 设置已有属性
dynamic duser = user;
user.UserId = Guid.NewGuid();
duser.Email = "19892257@qq.com";
user.Password = "YbSofteareFactory";
// 设置动态属性
duser.FriendUserName = "YB";
duser.CreatedDate = DateTime.Now;
duser.TodayNewsCount = 1;
duser.Age = 27.5;
duser.LastUpdateId = (Guid?)null;
duser.LastUpdatedDate=null;
// 动态属性值保存
ExtensionDataApi.SaveExtensionObject(user.UserId,user); // 从数据库中加载属性值
var obj = user.LoadExtensionData(user.UserId); // 测试是否加载正确
Assert.AreEqual(obj.FriendUserName, "YB");
Assert.IsNotNull(obj.CreatedDate);
Assert.AreEqual(obj.TodayNewsCount, 1);
Assert.AreEqual(obj.Age, 27.5);
Assert.IsNull(obj.LastUpdateId);
Assert.IsNull(obj.LastUpdatedDate);
var items = ExtensionDataApi.FindExtensionDataBy(user.UserId.ToString(), user);
//测试保存的动态属性数
Assert.IsTrue(items.Count() == 6);
// 修改动态属性值
duser.Age = 28;
// 新增动态属性
duser.Tag = null;
duser.NewProperty = 12;
//使用扩展方法进行保存动态属性值至数据库
user.SaveExtensionData(user.UserId); items = ExtensionDataApi.FindExtensionDataBy(user.UserId.ToString(), user);
//判断保存的属性数量是否正确
Assert.IsTrue(items.Count() == 8);
//使用扩展方法动态从数据库中加载属性
obj = user.LoadExtensionData(user.UserId);
Assert.AreEqual(obj.Tag, null);
Assert.AreEqual(obj.NewProperty, 12);
duser.ComplexObject = user;
//设置新值
duser.Tag = true;
ExtensionDataApi.SaveExtensionObject(user.UserId, user);
obj = ExtensionDataApi.LoadExtensionObject(user.UserId, user);
// 验证加载的属性新值是否正确
Assert.IsTrue(obj.Tag);
//返回对象数组的属性字典方法测试
var dic = ExtensionDataApi.FindExtensionDataDictionaryBy(new string[]{user.UserId.ToString()}, user.GetType().FullName);
Assert.IsTrue(dic.Count>0);
//byte[] 测试,对可方便存储文件、图片等内容
duser.Image = new byte[] {2, 255, 241, 236, 16, 19, 128, 32, 90};
ExtensionDataApi.SaveExtensionObject(user.UserId, user);
obj = ExtensionDataApi.LoadExtensionObject(user.UserId, user);
Assert.AreEqual(obj.Image.Length, 9);
Assert.AreEqual(obj.Image[8], 90);
//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

二、键值生成器

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

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

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

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

 IdGeneratorTest
/// <summary>
///GetNextID 的测试
///</summary>
[TestMethod()]
public void GetNextIDTest()
{
IdGeneratorApi.ClearAllIdGenerator();
var user = new User();
//生成类似 U-01-201308-001格式的ID,%A表示输出客户端编号,%D表示输出日期时间
var idGen = new IdGenerator()
{
Type = typeof (User).FullName,
DateFormat = "yyyyMM",
GenFormat = "U-%A-%D-",
Id = Guid.NewGuid(),
StartValue = 1,
NextValue = 1,
ValueLength = 3
};
//API基本方法测试
IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
var item = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
Assert.IsNotNull(item);
item = IdGeneratorApi.GetIdGeneratorBy(user);
Assert.IsNotNull(item);
item = IdGeneratorApi.GetIdGeneratorBy("not exist's record");
Assert.IsNull(item);
//API基本方法测试
Assert.IsTrue(IdGeneratorApi.IdGeneratorExists(user));
Assert.IsFalse(IdGeneratorApi.IdGeneratorExists("dkakd_test_a"));
//生成ID号
var str = IdGeneratorApi.GetNextID(user);
Assert.AreEqual("U-02-201308-001", str);
str = IdGeneratorApi.GetNextID(user);
Assert.AreEqual("U-02-201308-002", str);
idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
//无需生成日期,当前生成的ID号类似于U-02--003
idGen.DateFormat = string.Empty;
IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
//生成下一ID号
str = IdGeneratorApi.GetNextID(user);
Assert.AreEqual("U-02--003", str);
str = IdGeneratorApi.GetNextID(user);
Assert.AreEqual("U-02--004", str);
idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
// 如下代码修改生成的ID号类似于U-0005-
idGen.DateFormat = "yyyyMM";
//未设置%D,将不再输出日期
idGen.GenFormat = "U-%v-";
//修改生成编号的长度为4
idGen.ValueLength = 4;
IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
str = IdGeneratorApi.GetNextID(user);
Assert.AreEqual("U-0005-", str);
str = IdGeneratorApi.GetNextID(user);
Assert.AreEqual("U-0006-", str);
//API基本方法测试
IdGeneratorApi.DeleteIdGenerator(idGen);
item = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
Assert.IsNull(item);
item = IdGeneratorApi.GetIdGeneratorBy(user);
Assert.IsNull(item);
IdGeneratorApi.ClearAllIdGeneratorOfApplication();
}
/// <summary>
///GetNextGroupID 的测试,批量生产ID号
///</summary>
[TestMethod()]
public void GetNextGroupIDTest()
{
IdGeneratorApi.ClearAllIdGeneratorOfApplication();
var user = new User();
var idGen = new IdGenerator()
{
Type = typeof(User).FullName,
DateFormat = "yyyyMM",
GenFormat = "U-%a-%D-%v",
Id = Guid.NewGuid(),
StartValue = 1,
NextValue = 1,
ValueLength = 3
};
IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
//批量生成3个ID号
var str = IdGeneratorApi.GetNextGroupID(user,3);
Assert.IsTrue(str.Length==3);
Assert.IsTrue(str[0]=="U-02-201308-001");
Assert.IsTrue(str[1]=="U-02-201308-002");
Assert.IsTrue(str[2]=="U-02-201308-003");
idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
// 如下修改将生成类似于T0004的ID,将忽略日期和客户端编号
idGen.GenFormat = "T%v";
idGen.ValueLength = 4;
IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
str = IdGeneratorApi.GetNextGroupID(user,2);
Assert.IsTrue(str.Length==2);
Assert.IsTrue(str[0]=="T0004");
Assert.IsTrue(str[1]=="T0005");
idGen = IdGeneratorApi.GetIdGeneratorBy(idGen.Id);
//修改生成的ID格式
idGen.DateFormat = "yyyy";
//生成类似于01-0010/2013的ID号,%a为客户端编号,%v为流水号,%d将输出日期时间,此处为年份
idGen.GenFormat = "%a-%v/%d";
//指明流水号长度为4,类似于0001
idGen.ValueLength = 4;
IdGeneratorApi.SaveOrUpdateIdGenerator(idGen);
str = IdGeneratorApi.GetNextGroupID(user,2);
Assert.IsTrue(str.Length==2);
Assert.IsTrue(str[0]=="02-0001/2013");
Assert.IsTrue(str[1]=="02-0002/2013");
IdGeneratorApi.ClearAllIdGenerator();
}
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
}
IdGeneratorTest

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

 

Show 一下最新的动态属性扩展功能与键值生成器功能的更多相关文章

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

    YbSoftwareFactory 各种插件的基础类库中又新增了两个方便易用的功能:动态属性扩展与键值生成器,本章将分别介绍这两个非常方便的组件. 一.动态属性扩展 在实际的开发过程中,你肯定会遇到数 ...

  2. K-V-O 键值观察机制

    在两个不同的控制器之间传值是iOS开发中常有的情况,应对这种情况呢,有多种的应对办法.kvc就是其中的一种,所以,我们就在此解释之.   key value observing  键值观察,给人一种高 ...

  3. 浅谈Redis数据库的键值设计(转)

    丰富的数据结构使得redis的设计非常的有趣.不像关系型数据库那样,DEV和DBA需要深度沟通,review每行sql语句,也不像memcached那样,不需要DBA的参与.redis的DBA需要熟悉 ...

  4. 在C#中用Linq从属性文件中读取键值对Key-Value Pair

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:在C#中用Linq从属性文件中读取键值对Key-Value Pair.

  5. 分布式键值存储系统ETCD调研

    分布式键值存储系统ETCD调研 简介 etcd是一个开源的分布式键值存储工具--为CoreOS集群提供配置服务.发现服务和协同调度.Etcd运行在集群的每个coreos节点上,可以保证coreos集群 ...

  6. iOS中 KVO 键值观察者

    KVO Key-Value-Obsever 键值观察者 1.首先要有一个观察者,此时被观察者是自己找一个观察者观察自己的key值对应的value值有没有改变,如果改变了就可以做一些响应的操作 创建一个 ...

  7. docker——Etcd高可用键值对数据库

    一.简介 Etcd按照官方介绍: Etcd is a distributed, consistent key-value store for shared configuration and serv ...

  8. 浅谈REDIS数据库的键值设计(转)

    add by zhj: 关系数据库表的一条记录可以映射成Redis中的一个hash类型,其实数据库记录本来就是键值对.这样,要比本文中的键设计用更少的键,更节省内存,因为每个键除了它的键值占用内存外, ...

  9. ASP.NET MVC WebApi 返回数据类型序列化控制(json,xml) 用javascript在客户端删除某一个cookie键值对 input点击链接另一个页面,各种操作。 C# 往线程里传参数的方法总结 TCP/IP 协议 用C#+Selenium+ChromeDriver 生成我的咕咚跑步路线地图 (转)值得学习百度开源70+项目

    ASP.NET MVC WebApi 返回数据类型序列化控制(json,xml)   我们都知道在使用WebApi的时候Controller会自动将Action的返回值自动进行各种序列化处理(序列化为 ...

随机推荐

  1. PyCharm 使用简介

    PyCharm 使用简介 最近由于项目需要,领导要求使用python以方便扩展,没有办法,赶鸭子上架花了2天时间翻完了python的初级教程然后就开始写代码.有一款好的IDE可以帮助我快速上手一门新语 ...

  2. cocos2d 游戏开发实战

    文章转自:http://uliweb.clkg.org/tutorial/read/40 6   cocos2d 游戏开发实战 6.1   创建cocos2d项目 6.2   cocos2d v3 & ...

  3. archlinux的wiki非常强壮

    最近发现搜索linux工具或系统配置内容.来自同一个站点很多很好的资源:https://www.archlinux.org/,网站wiki(https://wiki.archlinux.org/)中有 ...

  4. SICP 锻炼 (1.45)解决摘要

    SICP 1.45是对前面非常多关于不动点的习题的总结. 题目回想了我们之前在1.3.3节使用的不动点寻找方法.当寻找y -> x/y 的不动点的时候,这个变换本身不收敛.须要做一次平均阻尼才干 ...

  5. 关于Java中List对象的分页思想,按10个或者n个数对list进行分组

    try { List<String> timelist = DateUtils.getDateListBySETime("2015-08-01", "2015 ...

  6. NM常用网络命令

    Ipconfig命令 Ipconfig命令可以被用来显示机器当前TCP/IP配置信息. 当使用Ipconfig时不带不论什么參数选项,那么它为每一个已经配置好的接口显示IP地址.子网掩码和默认网关值. ...

  7. Appium Android Bootstrap源码分析之控件AndroidElement

    通过上一篇文章<Appium Android Bootstrap源码分析之简介>我们对bootstrap的定义以及其在appium和uiautomator处于一个什么样的位置有了一个初步的 ...

  8. sb2-admin

    近期开发中遇到的问题总结   最近准备把后台管理系统重新设计开发下,使用了bootstrap,在网上找了个漂亮的后台模板:sb2-admin,在使用中遇到了不少问题,总结下,以免以后忘记. 1.EF5 ...

  9. PHP 9: 表达式

    原文:PHP 9: 表达式 本章介绍PHP的表达式.PHP的表达式其实和其他语言没有什么区别.普通的赋值是表达式,函数也是表达式,通过函数赋值也是.三元条件运算符也是,即: $first ? $sec ...

  10. C#6.0 中的那些新特性

    C#6.0 中的那些新特性 前言 VS2015在自己机器上确实是装好了,费了老劲了,想来体验一下跨平台的快感,结果被微软狠狠的来了一棒子了,装好了还是没什么用,应该还需要装Xarmain插件,配置一些 ...