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

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

一、动态属性扩展

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

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

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

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

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

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

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

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

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

二、键值生成器

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

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

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

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

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

    <table> <tr> <td><a href="javascript:void(0)" class="easyui-link ...

  2. Webbrowser控件史上最强技巧全集

    原文:Webbrowser控件史上最强技巧全集 Webbrowser控件史上最强技巧全集 VB调用webbrowser技巧集 1.获得浏览器信息: Private Sub Command1_Click ...

  3. winhec

    #winhec# 开发人员刷屏看点 (视频) 今天大家已经被winhec刷屏了,本来不想写这篇了,但看了所有的文章,大家关注的都是windows 10的那些新功能,小米win10刷机,联想千元手机,小 ...

  4. Font-Awesome 体验 鼠标进入图标变大

    <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> &l ...

  5. Redis 优化查询性能

    一次使用 Redis 优化查询性能的实践   应用背景 有一个应用需要上传一组ID到服务器来查询这些ID所对应的数据,数据库中存储的数据量是7千万,每次上传的ID数量一般都是几百至上千数量级别. 以前 ...

  6. [Unity3D]Unity3D连衣裙实现游戏开发系统

    大家好,我是秦培.欢迎关注我的博客,我的博客地址">blog.csdn.net/qinyuanpei. 不知从什么时候開始,国产RPG单机游戏開始出现换装,仙剑系列中第一部实现了换装的 ...

  7. java设计模式之七装饰器模式(Decorator)

    顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例,关系图如下: Source类是被装饰类,Decorator类是一个 ...

  8. ZooKeeper 主要的操作演示样品

    // 创建一个与server的连接 ZooKeeper zk = new ZooKeeper("localhost:" + CLIENT_PORT, ClientBase.CONN ...

  9. servlet请求转发与重定向的差别------用生活实例来形象说明两者的差别

    1,请求重定向:client行为,response.sendRedirect(),从本质上讲等同于两次请求,前一次的请求对象不会保留,地址栏的URL地址会改变. 2,请求转发:server行为,req ...

  10. SQL点滴19—T-SQL中的透视和逆透视

    原文:SQL点滴19-T-SQL中的透视和逆透视 透视 今天抽一点时间来看看透视和逆透视语句,简单的说就是行列转换.假设一个销售表中存放着产品号,产品折扣,产品价格三个列,每一种产品号可能有多种折扣, ...