使用CodeSmith的MiniAccess的实体生成模板,可以根据数据库的表结构和表间关系,一次性生成所有数据访问实体类型,生成同时默认的OR映射文件的内容。

一、MiniEntity.cst的功能

MiniEntity.cst包括两个参数

  1. SourceTable:必须,要生成实体的表。
  2. AccessClassName:必须,实现了IAccess的类,一般是从AccessBase<T>派生出来的对应某个数据库的实体访问类。

生成的实体类型

  1. 每个表对应一个实体。
  2. 默认的实体名称是“<表名>Entity”。如果表名中包含空格则替换成下划线,如果表名第一个字母不是大写则改成大写,使类名满足Pascal大小写规范。
  3. 实体类的基类为:EntityBase< 实体类型 >。
  4. 实体类均包括了默认构造函数。
  5. 实体类的命名空间由模板的Namespace属性确定,一般来说使用的命名空间是“<的数据库的名字>.EntityAccess”。
  6. 实体类都使用了部分类的声明,因而如果需要添加成员可以单独写到另外的类文件中。

实体类的实例属性

  1. 对于表中的每一个字段都生成一个私有变量。
  2. 对于表中的每一个字段都生成一对get/set属性,用来存取对应的私有变量。
  3. 目前所支持的字段类型包括(括号中表示的是映射的CSharp实体类型):AnsiString(string)、AnsiStringFixedLength(string)、Binary(byte[])、Boolean(bool)、Byte(byte)、Currency(decimal)、Date(DateTime)、DateTime(DateTime)、Decimal(decimal)、Double(double)、Guid(Guid)、Int16(short)、Int32(int)、Int64(long)、Object(object)、SByte(sbyte)、Single(float)、String(string)、StringFixedLength(string)、Time(TimeSpan)、UInt16(ushort)、UInt32(uint)、UInt64(ulong)和VarNumeric(decimal)等DbType类型。DbType类型和具体数据库本地类型之间的对应关系需要查看Ado.Net的相关文档。
  4. 如果字段为数值类型而且允许为空,那么实体类型用Nullable泛型类型表示,如“int?”。

实体类的实例方法

  1. 主键映射:获取子表记录的集合。函数名以Get打头,后面接着是外键表的名称,最后是Entities后缀。
  2. 外键映射:获取外键对应的实体。函数名以Get打头,后面部分是外键的名称,如果外键的名称以"FK_<表名>_"开始,则只取后半部分的名称。

实体类的静态方法:

  1. Fetch:根据主键值从数据库中取出一个实体,若没有记录返回null。 方法的参数是主键的值。
  2. Delete:根据主键值删除数据库中的记录,返回所删除的记录数,若没有记录返回0。 方法的参数是主键的值。
  3. FetchBy…:根据唯一索引查找实体。FetchBy后面紧跟表示唯一索引名称的字符串,如果唯一索引的命名是以“UIX_<表名>_”为前缀,那么在FetchBy后面的索引名称中要去掉该前缀。方法的参数依次是唯一索引的各个列。
  4. DeleteBy…:根据唯一索引删除实体。方法的命名和FetchBy一样,参数也和Fetch中一样。
  5. GetAllEntities:获取表中的所有记录对应的实体。

二、MiniAccess.cst的功能

MiniEntity.cst只单一的生成一个表的实体类型,而MiniAccess.cst的功能通过对每张表循环引用MiniEntity.cst,一次就生成多张表的实体类型。另外MiniAccess.cst还生成了OR映射文件的内容和Access类。MiniAccess.cst的属性有:

  1. Database:必须。指定数据库。
  2. Namespace:必须。实体访问类型的命名空间。
  3. DeveloperName:必须。开发者的名字,格式是:名字.姓氏。<!--EndFragment-->
  4. Provider:可选。映射文件中的提供者元素的名称,如果不指定,则默认是数据库名。
  5. AccessClass:可选。实现IAcess接口的类的名称,如果不指定,则默认使用是<数据库名>后面跟Access后缀。
  6. Tables:可选。指定需要映射的表的名称,多个表用逗号分开。如果不指定,表示映射数据库中的所有表。

三、使用Codesmith的Project.csp文件来定制执行实体生成模板

代码举例如下:

   1:  <?xml version="1.0"?>
   2:  <codeSmith xmlns="http://www.codesmithtools.com/schema/csp.xsd">
   3:    <propertySets>
   4:      <propertySet output="MyOutputCodeFile.cs" template="..\..\CodeSmithTemplate\MiniAccess.cst">
   5:        <property name="DeveloperName">MyName</property>
   6:        <property name="Database">
   7:          <connectionString>..connection string..
   8:          </connectionString>
   9:          <providerType>SchemaExplorer.SqlSchemaProvider,SchemaExplorer.SqlSchemaProvider</providerType>
  10:        </property>
  11:        <property name="Namespace">MyDatabase.EntityAccess</property>
  12:        <property name="Provider"></property>
  13:        <property name="AccessClass"></property>
  14:        <property name="Tables"></property>
  15:      </propertySet>
  16:    </propertySets>
  17:  </codeSmith>

*.csp文件在windows的资源管理器和visual studio的解决方案资源管理器中都可以直接打开运行。运行的结果就是生成了数据库的实体访问代码源文件。

四、一些技术点的讨论

  1. 关于主键的处理
    • 主键有两种情况,一是主键不可改写,如标识列(不可在插入时赋初值)和GUID(可以在插入时赋初值)等,另一种情况是可以改写,如客户编码的字段。
    • 不论哪种情况,主键所对应的属性本身都是可读可写的,因为从数据库读取时也需要赋值(注:仅通过构造可以避免这个问题,但是会带来一些复杂性,因而暂不考虑)。
    • 删除和更新实体时,需要通过主键的值来定位数据库中的记录,所以如果修改了主键的值,那么必须把原来的值保存起来。
    • entitybase使用IDictionary<string, object> oldPropertyValues来保存字段的原始值,用这个方式实现了整个实体的状态管理,可以根据需要Cancel对实体的修改。
    • 借助实体的oldPropertyValues,另外在实体中保存主键对应的属性名称,这样在update和delete的时候,就可以根据key的名称找到原来的值
  2. Entity都继承了EntityBase中的一些成员,在使用Entity实例时能带来便利。
    • IsNew:可读可写,是否新建的实体,即数据库中没有对应的记录。
    • IsDelete:可读可写,实体是否被标识为删除。
    • KeyPropertyNames:只读。字符串数组,表示主键字段所对应的属性名称。
    • Save():根据实体状态增删改数据库中的记录。查询数据时不要忘了要带上“WHERE”关键字,当然也可以增加排序、连接等谓词。
  3. 类的声明全部采用部分类,便于把手工书写的代码分离出去。 一般建议书写一个和DatabaseAccess.cs对应的MyDatabaseAccess.cs文件,其中书写部分类中增加的功能。
  4. 如何在实体类中生成枚举类型的属性?在部分类中增加枚举类型的get/set方法,但是不定义对应的私有成员,而是直接引用字段所对应的整形或者字符串形式的私有成员。

CodeSmith模板的开发记录

  1. (已修改)2008-10-10:生成的源代码中EntitySate,应该改为EntityState。
  2. (已修改)2008-10-10:返回键字段的原始值的表达式return this.oldwordWord...应该改为return this.oldWordWord...
  3. (已修改)(已修改2008-10-27):根据唯一索引生成查找和删除的方法。
    • FetchByIndex方法
    • DeleteByIndex方法
    • 如果表具备多个唯一索引(一般来说,表没有必要设多个唯一索引),则后面生成的函数名为...ByIndexN,N为1、2、等数字。
    • 对唯一索引的命名使用...ByIndexN的方式可读性不好,改为By<索引名称>。 (已修改)实体类中集合的命名空间改为:using System.Collections.Generic;
  4. (已修改)2008-10-17:无主键表无法生成实体类。应该为:去掉根据主键的操作Delete和Update和Fetch,但是保留根据唯一索引的FetchBy<索引名称>和DeleteBy<索引名称>。注意:根据唯一索引的Update目前还没有实现,难点是Old值的保留,该功能似乎用处不大,先不考虑。

  5. (已修改)2011-06-03:当主键对应外键的名称和主键的名称不一致时出错。
  6. (尚未修改)当一个主键对应一个表中的两个外键时出错(即存在两个一对多关系)。