YbSoftwareFactory 代码生成插件【十九】:实体类配合数据库表字段进行属性扩展的小技巧

实体类通常需要和数据库表进行了ORM映射,当你需要添加新的属性时,往往同时也需要在数据库中添加相应的字段并配置好映射关系,同时可能还需对数据访问组件进行重新编译和部署才能有效。而当你开始设计一个通用数据访问组件后,因为项目需求的不同和需求的不断变化演变,很难不能保证不会再添加额外的属性和字段。特别是项目部署运行后,添加一个属性和字段带来的额外维护的工作量可能要远远超过对代码进行调整的工作量。本文提供了属性字段扩展的一种思路,在满足核心字段可通过实体类强类型进行访问的同时,还可通过C# 4.0提供的dynamic特性和Dictionary等技术手段进行字段、属性的扩展,并对数据访问的统一封装,具有通用性强、使用方便、扩展能力强等优点。

本文用到了前面提到的ExtensionObject,其是进行属性扩展原理的核心类,该类继承自DynamicObject类,并实现了, IDynamicMetaObjectProvider,IDictionary<string,object>等接口。和.NET Framework中ExpandoObject类不同的是,继承自DynamicObject的类可以添加实例属性,而ExpandoObject因为被设计为“sealed”类,因此它只能在运行时动态添加属性;另外,继承自DynamicObject的类可实现自定义的对其成员进行管理的一系列方法,因此和ExpandoObject类相比,从DynamicObject类继承无疑具有更高的灵活性。对ExtensionObject类的实现不清楚的可先看看前面的文章:http://www.cnblogs.com/gyche/p/3223341.html

在YbSoftwareFactory的一些底层数据访问组件中,例如ConcreteData字典、HierarchyData字典、组织机构实体类、权限实体类、用户信息实体类、角色定义实体类等均已继承自ExtensionObject并实现了对应的对扩展的字段进行数据访问和管理的方法,从实际的运用效果来看,在字段、属性的扩展上确实是非常的灵活和方便。

动态属性扩展的步骤如下:

1、首先,通过让实体类继承自“ExtensionObject”,因为ExtensionObject继承自DynamicObject,并实现了IDictionary<string,object>和索引器,这样实体类就具有了动态属性的自管理功能,在通过强类型访问其实例属性的同时,也能通过dynamic,IDictionary接口和索引器访问其实例属性和动态属性。

例如定义一个用户类并添加必要的实例属性如下:

[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()
       { }

       public User(object instance)  : base(instance)
       {
       }
}

然后,就可通过如下方式进行实例属性和动态属性的访问,是不是非常灵活和方便:

 1  var user = new User();
 2 // 通过实例属性进行访问
 3 user.UserId = Guid.NewGuid();
 4 user.Password = "YbSofteareFactory";
 5 //通过动态方式进行实例属性的访问
 6 dynamic duser = user;
 7 duser.Email = "[email protected]";
 8 // 追加动态属性
 9 duser.FriendUserName = "YB";
10 duser.CreatedDate = DateTime.Now;
11 duser.TodayNewsCount = 1;
12 duser.Age = 27.5;
13 duser.LastUpdateId = (Guid?)null;
14 duser.LastUpdatedDate = null;
15 //通过索引器也可进行实例属性和动态属性的访问和追加
16 user["LastUpdatedDate"] = DateTime.Now;

2、实现对扩展字段的数据库访问:

  1          #region 加载扩展属性
  2
  3         /// <summary>
  4         /// 为指定的ConcreteData集合加载扩展属性
  5         /// </summary>
  6         /// <param name="items">待加载的ConcreteData集合</param>
  7         public override void LoadExtPropertiesFor(IEnumerable<ConcreteData> items)
  8         {
  9             //判断是否需要加载
 10             //_extFields为需加载的字段名称字符串,如“NewField1,NewField2”,通过config文件进行配置。
 11             if (_extFields.Length > 0 && items.Any() )
 12             {
 13                 //转换为字典,方便后续进行处理
 14                 var dic = items.ToDictionary(c => c.ConcreteDataId);
 15                 //组合标识字符串
 16                 var ids = string.Format("‘{0}‘",string.Join("‘,‘",dic.Keys.ToArray()));
 17                 using (HostingEnvironment.Impersonate())
 18                 using (var db = this.connectionStringSetting.CreateDbConnection())
 19                 using (var cmd =
 20                     this.CreateDbCommand(string.Format("SELECT ConcreteDataId,{0} FROM $TableName WHERE ConcreteDataId IN ({1})",_extFields,ids), db))
 21                 {
 22                     cmd.AddParameterWithValue("@ids", ids);
 23                     db.Open();
 24                     using (var r = cmd.ExecuteReader())
 25                     {
 26                         while (r.Read())
 27                         {
 28                             //获取标识
 29                             var concreteDataId = r["ConcreteDataId"] as string;
 30                             //根据字典获取待加载动态属性值的实体
 31                             var item = dic[concreteDataId];
 32                             foreach (var extField in _extFieldArr)
 33                             {
 34                                 var value = r[extField];
 35                                 //通过ExtensionObject类的索引器设置动态属性及相应的值
 36                                 item[extField] = value != DBNull.Value ?value:null;
 37                             }
 38                         }
 39                     }
 40                 }
 41             }
 42         }
 43
 44         #endregion
 45
 46         #region 保存扩展属性
 47
 48         public override void SaveExtPropertiesFor(IEnumerable<ConcreteData> items)
 49         {
 50             if (_extFields.Length > 0)
 51             {
 52                 //获取待更新扩展属性的SQL更新语句
 53                 var updateSql = string.Join(",", _extFieldArr.Select(c => string.Format("{0} = @{0}", c)));
 54
 55                 using (HostingEnvironment.Impersonate())
 56                 using (var db = this.connectionStringSetting.CreateDbConnection())
 57                 using (
 58                     var cmd =
 59                         this.CreateDbCommand(
 60                             string.Format("UPDATE $TableName SET {0} WHERE ConcreteDataId = @ConcreteDataId",
 61                                           updateSql), db))
 62                 {
 63                     db.Open();
 64                     DbTransaction sqlTransaction = db.BeginTransaction();
 65                     cmd.Transaction = sqlTransaction;
 66                     try
 67                     {
 68                         foreach (var item in items)
 69                         {
 70                             cmd.Parameters.Clear();
 71                             cmd.AddParameterWithValue("@ConcreteDataId",item.ConcreteDataId);
 72                             foreach (var extField in _extFieldArr)
 73                             {
 74                                 if (item.Contains(extField, true) && item[extField] != null)
 75                                 {
 76                                     //如果实体的属性包含配置的字段名,则追加更新参数及值
 77                                     cmd.AddParameterWithValue(string.Format("@{0}", extField),
 78                                                               item[extField]);
 79                                 }
 80                                 else
 81                                 {
 82                                     //如果实体的属性不包含配置的字段名,则取消对该字段的更新
 83                                     cmd.CommandText = cmd.CommandText.Replace(string.Format("@{0}", extField), "NULL");
 84                                 }
 85                             }
 86                             cmd.ExecuteNonQuery();
 87                         }
 88                         //进行事务提交
 89                         sqlTransaction.Commit();
 90                     }
 91                     catch (Exception)
 92                     {
 93                         sqlTransaction.Rollback();
 94                         throw;
 95                     }
 96                 }
 97             }
 98
 99         }
100
101         #endregion

3、为了更方便使用,我们在此处可进一步封装,我们可在所配置的Provider初始化时把config文件中配置好的extFields读出来即可,如下是初始化方法的实现:

 1         #region Initialize
 2
 3         public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
 4         {
 5             // Validate arguments
 6             if (config == null) throw new ArgumentNullException("config");
 7             if (string.IsNullOrEmpty(name)) name = "YbConcreteDataProvider";
 8             if (String.IsNullOrEmpty(config["description"]))
 9             {
10                 config.Remove("description");
11                 config.Add("description", "Yb concrete data provider");
12             }
13             //判断是否存在tableName属性
14             if (String.IsNullOrEmpty(config["tableName"]))
15             {
16                 config.Remove("tableName");
17                 //添加默认的表名
18                 config.Add("tableName", "YbConcreteData");
19             }
20             //判断是否存在extFields属性
21             if (string.IsNullOrEmpty(config["extFields"]))
22             {
23                 config.Remove("extFields");
24                 //不存在则可设置为"",这样将不会对任何扩展的新字段进行访问
25                 config.Add("extFields", "");
26             }
27             // Initialize base class
28             base.Initialize(name, config);
29
30             // Read connection string
31             this.ConnectionStringName = config.GetConfigValue("connectionStringName", null);
32             if (string.IsNullOrWhiteSpace(this.ConnectionStringName))
33                 throw new ConfigurationErrorsException(Resources.Required_connectionStringName_attribute_not_specified);
34             this.connectionStringSetting = ConfigurationManager.ConnectionStrings[this.ConnectionStringName];
35             if (this.connectionStringSetting == null)
36                 throw new ConfigurationErrorsException(string.Format(Resources.Format_connection_string_was_not_found,
37                                                                      this.ConnectionStringName));
38             if (string.IsNullOrEmpty(this.connectionStringSetting.ProviderName))
39                 throw new ConfigurationErrorsException(
40                     string.Format(
41                         Resources.Format_connection_string_does_not_have_specified_the_providerName_attribute,
42                         this.ConnectionStringName));
43
44             //激发设置连接字符串前的事件处理程序,主要目的是解密连接字符串
45             ConnectionStringChangingEventArgs args =
46                 RaiseConnectionStringChangingEvent(connectionStringSetting.ConnectionString);
47             if (args == null) throw new ProviderException(Resources.Connection_string_cannot_be_blank);
48             if (!this.connectionStringSetting.ConnectionString.Equals(args.ConnectionString))
49             {
50                 this.connectionStringSetting =
51                     new ConnectionStringSettings(this.ConnectionStringName, args.ConnectionString,
52                                                  this.connectionStringSetting.ProviderName);
53             }
54             if (string.IsNullOrWhiteSpace(connectionStringSetting.ConnectionString))
55                 throw new ProviderException(Resources.Connection_string_cannot_be_blank);
56
57             this.applicationName = config["applicationName"];
58             //获取配置文件中配置的数据库实际表名
59             this.tableName = config["tableName"];
60             SecUtility.CheckParameter(ref tableName, true, true, true, 256, "tableName");
61             //获取配置文件中配置的新扩展的字段名集合
62             _extFields = config.Get("extFields").Trim();
63             if (!string.IsNullOrEmpty(_extFields))
64             {
65                 //进行字符串分割,转换为字段数组,方便后续的处理
66                 _extFieldArr = _extFields.Split(new[] {‘,‘}, StringSplitOptions.RemoveEmptyEntries);
67                 _extFieldArr = _extFieldArr.Select(c => c.Trim()).ToArray();
68             }
69         }
70
71         #endregion

最后看看单元测试代码可进一步理解其调用的具体过程,在数据库中扩展的字段名仅需在config配置文件中设置即可生效,同时在调用方式上进行了统一,最终无需传递扩展的字段名称、类型等参数,在实体对象中也能获取和设置这些新添加的属性的值。单元测试代码如下(此处扩展了三个字段:“NewField1”,“NewField2”,“NewField3”,类型分别为string,bool,DateTime):

 1         /// <summary>
 2         ///UpdateConcreteData 的测试
 3         ///</summary>
 4         [TestMethod()]
 5         public void ConcreteData_UpdateConcreteDataTest()
 6         {
 7             ConcreteData concreteData = MyConcreteData; // TODO: 初始化为适当的值
 8             bool expected = true; // TODO: 初始化为适当的值
 9             bool actual;
10             actual = ConcreteDataApi.UpdateConcreteData(concreteData);
11             Assert.AreEqual(expected, actual);
12
13        //保存扩展属性值为null
14             ConcreteDataApi.SaveExtPropertiesFor(concreteData);
15             ConcreteDataApi.LoadExtPropertiesFor(concreteData);
16             Assert.IsNull(concreteData["NewField1"]);
17             Assert.IsNull(concreteData["NewField2"]);
18             Assert.IsNull(concreteData["NewField3"]);
19        //设置扩展属性值
20             concreteData["NewField1"]="123";
21             concreteData["NewField2"] = true;
22             concreteData["NewField3"] = DateTime.Now;
23             ConcreteDataApi.SaveExtPropertiesFor(concreteData);
24             var item = ConcreteDataApi.GetConcreteDataWithExtProperties(concreteData.ConcreteDataId);
25             Assert.AreEqual(item["NewField1"],"123");
26             Assert.AreEqual(item["NewField2"],true);
27             Assert.IsNotNull(item["NewField3"]);
28
29             concreteData["NewField1"] = "";
30             concreteData["NewField2"] = null;
31             concreteData["NewField3"] = null;
32             ConcreteDataApi.SaveExtPropertiesFor(concreteData);
33             item = ConcreteDataApi.GetConcreteDataWithExtProperties(concreteData.ConcreteDataId);
34             Assert.IsNull(item["NewField1"]);
35             Assert.IsNull(item["NewField2"]);
36             Assert.IsNull(item["NewField3"]);
37         }

通过上述设计,确保了每个数据访问组件默认情况下只需加载必要的字段(即实体类的实例属性),并预留了对新扩展字段的数据访问接口,在提高了灵活性和可扩展性的同时,还兼顾了性能方面的考虑。

下一章将介绍对扩展自ExtensionObject的对象进行Json序列化的具体实现,这样就可让ExtensionObject和MVC实现完美的集成,而无需再进行中间层次的模型转换。

附一:ExtensionObject源码

附二:YbSoftwareFactory底层组件帮助文档

附三:权限模型Demo

YbSoftwareFactory 代码生成插件【十九】:实体类配合数据库表字段进行属性扩展的小技巧

时间: 2024-10-05 12:00:49

YbSoftwareFactory 代码生成插件【十九】:实体类配合数据库表字段进行属性扩展的小技巧的相关文章

Hibernate实体类与数据表字段默认值之间的优先关系

在日常的应用开发过程中,我们一般都会使用对象关系映射来通过面向对象的编程模型来解决数据库的操作,我们生产过程当中使用较多的当属Hibernate框架了,他非常灵活,为我们提供了多种方式来实现数据层的操作与管理.通过实体类与数据表的映射,实体类对应表,属性对应字段就可以将我们想要生产的对象赋值到数据库,但是我们有时也需要查询对象,但是无论什么操作,都必须通过实体类来传递数据. 有时我们数据库有一些字段是数据库自动赋值,禁止用户通过程序修改的,如记录生成时间,那么如果你实体类按照正常情况下,你为了查

YbSoftwareFactory 代码生成插件【二十】:DynamicObject的序列化

DynamicObject 是 .NET 4.0以来才支持的一个类,但该类在.NET 4.0下未被标记为[Serializable] Attribute,而在.NET 4.5下则被标记了[Serializable] Attribute.需要注意的是,如果你使用需要进行XML序列化等操作(例如WCF中),部署到未安装.NET 4.5的环境中通常会报错并提示异常,而不管你编译时使用的目标平台是.NET 4.0 还是 .NET 4.5.通常这个错误在安装了.NET 4.5环境的开发机上通常没有问题,即

YbSoftwareFactory 代码生成插件【十八】:树形结构下的查询排序的数据库设计

树形结构的排序在中国特色下十分普遍也非常重要,例如常说的五大班子,党委>人大>政府>政协>纪委,每个班子下还有部门,岗位,人员,最终排列的顺序通常需要按权力大小.重要性等进行排列,顺序排列不好可是重大的罪过,领导很生气,后果很严重.这种排序方式本质上就是典型的树形结构深度排序,但在数据库中很难直接通过SQL语句简单高效地进行处理,更不用说还要支持不同类型数据库了. 当前解决此类问题,主要有两种方法. 1. 排序码方式 原理:在每个树形节点上均设置一个排序码,排序码通常是一个字符串并

实体类与数据库字段不匹配问题,java.sql.SQLSyntaxErrorException: Unknown column &#39;xxx&#39; in &#39;field list&#39;

控制台报错 ### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'user_name' in 'field list' ### The error may exist in panfeng/mapper/StudentMapper.java (best guess) ### The error may involve panfeng.mapper.StudentMapper.se

YbSoftwareFactory 代码生成插件【二十二】:CMS基础功能的实现

很多网友建议在YbRapidSolution for MVC框架的基础上实现CMS功能,以方便进行内容的管理,加快前端页面的开发速度.因此花了一段时间,实现了一套CMS内容发布系统并已集成至YbRapidSolution for MVC框架中. 本CMS当前实现了CMS参数设置.栏目管理.文章管理.文档管理.评论管理.问卷调查等功能.首先看看本CMS使用的主要技术及其整体架构图: 上图中的架构可以说和YbRapidSolution for MVC的架构基本是一致的,如下将对主要的技术要点进行总结

YbSoftwareFactory 代码生成插件【二十一】:Web Api及MVC性能提升的几个小技巧

最近在进行 YbSoftwareFactory 的流程功能升级,目前已经基本完成,现将用到的一些关于 Web Api 及 MVC 性能提升的一些小技巧进行了总结,这些技巧在使用.配置上也相当的简单,但通常都能大大提高应用程序的性能,希望对大家有用. 一.缓存 为了避免每次请求都去访问后台的资源,我们一般会考虑将一些更新不是很频繁的,可以重用的数据,通过一定的方式临时地保存起来,后续的请求根据情况可以直接访问这些保存起来的数据,这种机制就是所谓的缓存机制.缓存分为页面输出缓存,内存数据缓存和缓存依

ef-codefirst方式配置实体类,生成数据库

做项目的时候,如果我们如果用orm方式来做数据库持久化操作的话.微软官方首先会向我们推荐ef,而我们用ado.net的话,似乎也需要建立实体类来接传值,那么我们用codefirst就有一举两得的效果了 1.在对应的项目安装ef 2.创建实体类 3.接下来需要配置字段的属性,也就是长度,类型之类的,那么string类型一般对应数据库中的nvarchar类型,还有要建立实体类之间的对应关系,就是配置导航属性,这里有注解式和fluent Api两种方式来配置,我建议用fluent Api的方式来配置,

spring boot 从入门到精通( 二)通过实体类生成数据库表的方式

1. 需要添加的依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-con

hibernate插入实体类【数据库默认值】不生效的问题

第一个问题老生常谈 如果表结构设置默认值 数据库实体类不对该字段进行复制的情况下插入数据记录 字段直接以数据库设置的默认值进行插入hibernate本身可以根据XML配置进行生效此配置 1 <hibernate-mapping 2     package="*.entity"> 3     <class name="ProEntity" table="PRODUCTINFO" dynamic-insert="true&