NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode。

整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含多年开发经验于其中。

开源地址:https://github.com/NewLifeX/X (求star, 620+)

XCode代码生成的最佳实践是 Build.tt + Model.xml ,码神工具仍可生成实体类代码,但缺乏维护可能有兼容问题。

代码生成

数据模型文件是XCode数据库开发的中心,曾经流行和支持的DB First和Entity First,经过10多年优胜劣汰,只剩下Model First。

XCode的数据模型文件就是一个Model.xml,(名字可变),同目录配套Build.tt,用于在vs里调用XCode生成基于xml模型文件的多个实体类文件。
核心用法,在build.tt文件上点击右键选择“运行自定义工具”,即可根据model.xml生成多个实体类文件!
image.png

Nuget安装模版

从nuget安装NewLife.XCode时,可以看到项目下多了一个Model.xml和build.tt/build_netcore.tt

nfx项目安装NewLife.XCode才有,netcore项目可从nfx项目中拷贝这两个文件,或者修改项目为netcore+net45混合项目后再安装引用。

(!!!安装XCode后,编译一次项目,确保输出目录中存在newlife.core.dll和xcode.dll,因为build.tt内需要引用这两个文件,也可以拷贝这两个文件到别处并修改build.tt内部引用)

数据模型与代码生成 - 图2

直接下载模版

可以直接下载 https://x.newlifex.com/XCode_BuildModel.zip ,确保build.tt/build_netcore.tt文件属性中的自定义工具为 TextTemplatingFileGenerator 。
解压拷贝model.xml和build.tt(netcore项目选build_netcore.tt)到需要放置实体类的目标目录。
image.pngimage.png

手工编写模版(解决.NET5.0报错)

.tt文件本质上就是VisualStudio的T4模版,可以在vs中新建一个,并确保其*文件属性中的自定义工具为 TextTemplatingFileGenerator
build.tt和model.xml的文件名都可以随意修改,build.tt默认会自动查找当前目录的第一个xml文件来使用。或者修改build.tt文件内容,强制指定文件名

  1. <#@ template language="C#" hostSpecific="true" debug="true" #>
  2. <#@ assembly name="netstandard" #>
  3. <#@ assembly name="$(SolutionDir)\DLL\NewLife.Core.dll" #>
  4. <#@ assembly name="$(SolutionDir)\DLL\XCode.dll" #>
  5. <#@ import namespace="System.Diagnostics" #>
  6. <#@ import namespace="System.IO" #>
  7. <#@ import namespace="XCode.Code" #>
  8. <#@ output extension=".log" #>
  9. <#
  10. // 设置当前工作目录
  11. PathHelper.BasePath = Host.ResolvePath(".");
  12. // 导入模型文件并生成实体类,模型文件、输出目录、命名空间、连接名、中文文件名、表名字段名大小写
  13. //EntityBuilder.Build(String xmlFile = null, String output = null, String nameSpace = null, String connName = null, Boolean? chineseFileName = true,Boolean? nameIgnoreCase = null);
  14. EntityBuilder.Build();
  15. //var tables = DAL.ImportFrom("Company.Project.xml");
  16. //EntityBuilder.Build(tables);
  17. #>

在.NETCore3.1/.NET5.0项目使用XCode的build_netcore.tt模板生成时,可能会报以下错误(未能找到文件):
image.png
或者报以下错误(正在编译转换:类型”Object”在未引用的程序集中定义。必须添加对程序集的引用):
image.png
这是VisualStudio2019的T4模板存在对.NET5.0程序集的兼容问题,只需要强制指定DLL引用位置即可。
上面文件中指定使用解决方案下DLL子目录的NewLife.Core.dll和XCode.dll,需要首先编译任意引用了XCode的项目,并在输出目录中寻找这两个文件,拷贝到DLL子目录中去。
此外,还可以修改 EntityBuilder.Build 参数来满足个性化需求。

NETCore项目模版生成报错

在.NETCore3.1/.NET5.0项目使用XCode的build.tt模板生成时,会报错:

此时我们可以修改模板

码神工具导出模型

码神工具下载地址 http://x.newlifex.com/XCoder_Install.exe
安装后启动,菜单中选择“数据建模工具”,会自动列出本机安装的数据库。
(也可以修改配置文件XCoder.exe.config,加入开发数据库的连接信息)
image.png
选择并连接数据库,可以看到该库的所有表信息,此时只需要“导出模型”,即可得到该库的model.xml模型文件。
image.png
(码神工具在早期2002~2012是XCode的唯一代码生成方式,现在仍可以使用,但由于缺乏维护可能存在一些兼容问题,推荐使用最新的build.tt+model.xml)

数据模型文件

Model.xml正是供我们进行数据建模的参考,同时也是XCode内部Membership的模型文件。

<?xml version="1.0" encoding="utf-8"?>
<Tables Version="9.6.6663.16294" NameSpace="XCode.Membership" ConnName="Membership" Output="" BaseClass="Entity"
         xmlns="http://www.newlifex.com/ModelSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="http://www.newlifex.com https://raw.githubusercontent.com/NewLifeX/X/master/XCode/ModelSchema.xsd" >
  <Table Name="User" Description="用户" DbType="SqlServer" RenderGenEntity="true">
    <Columns>
      <Column Name="ID" DataType="Int32"  Identity="True" PrimaryKey="True" Description="编号" />
      <Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名称。登录用户名" />
      <Column Name="Password" DataType="String" Description="密码" />
      <Column Name="DisplayName" DataType="String" Description="昵称" />
      <Column Name="Sex" DataType="Int32" Description="性别。未知、男、女" Type="SexKinds" />
      <Column Name="Mail" DataType="String" Description="邮件" />
      <Column Name="Mobile" DataType="String" Description="手机" />
      <Column Name="Code" DataType="String" Description="代码。身份证、员工编号等" />
      <Column Name="Avatar" DataType="String" Length="200" Description="头像" />
      <Column Name="RoleID" DataType="Int32" Description="角色。主要角色" />
      <Column Name="RoleIDs" DataType="String" Length="200" Description="角色组。次要角色集合" />
      <Column Name="Online" DataType="Boolean" Description="在线" />
      <Column Name="Enable" DataType="Boolean" Description="启用" />
      <Column Name="Logins" DataType="Int32" Description="登录次数" />
      <Column Name="LastLogin" DataType="DateTime" Description="最后登录" />
      <Column Name="LastLoginIP" DataType="String" Description="最后登录IP" />
      <Column Name="RegisterTime" DataType="DateTime" Description="注册时间" />
      <Column Name="RegisterIP" DataType="String" Description="注册IP" />
    </Columns>
    <Indexes>
      <Index Columns="Name" Unique="True" />
      <Index Columns="RoleID" />
    </Indexes>
  </Table>
  <Table Name="Role" Description="角色" RenderGenEntity="true">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
      <Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名称" />
      <Column Name="IsSystem" DataType="Boolean" Description="系统。用于业务系统开发使用,不受数据权限约束,禁止修改名称或删除" />
      <Column Name="Remark" DataType="String" Description="说明" />
      <Column Name="Permission" DataType="String" Length="500" Description="权限。对不同资源的权限,逗号分隔,每个资源的权限子项竖线分隔" />
      <Column Name="CreateUserID" DataType="Int32" Description="创建用户" />
      <Column Name="CreateIP" DataType="String" Description="创建地址" />
      <Column Name="CreateTime" DataType="DateTime" Description="创建时间" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新用户" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
      <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" />
    </Columns>
    <Indexes>
      <Index Columns="Name" Unique="True" />
    </Indexes>
  </Table>
  <Table Name="Menu" Description="菜单" BaseType="EntityTree" RenderGenEntity="true">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
      <Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名称" />
      <Column Name="DisplayName" DataType="String" Description="显示名" />
      <Column Name="FullName" DataType="String" Length="200" Description="全名" />
      <Column Name="ParentID" DataType="Int32" Description="父编号" />
      <Column Name="Url" DataType="String" Length="200" Description="链接" />
      <Column Name="Sort" DataType="Int32" Description="排序" />
      <Column Name="Icon" DataType="String" Description="图标" />
      <Column Name="Visible" DataType="Boolean" Description="可见" />
      <Column Name="Necessary" DataType="Boolean" Description="必要。必要的菜单,必须至少有角色拥有这些权限,如果没有则自动授权给系统角色" />
      <Column Name="Permission" DataType="String" Length="200" Description="权限子项。逗号分隔,每个权限子项名值竖线分隔" />
      <Column Name="Remark" DataType="String" Length="200" Description="备注" />
    </Columns>
    <Indexes>
      <Index Columns="Name" />
      <Index Columns="ParentID,Name" Unique="True" />
    </Indexes>
  </Table>
  <Table Name="Log" Description="日志" ConnName="Log" RenderGenEntity="true">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
      <Column Name="Category" DataType="String" Description="类别" />
      <Column Name="Action" DataType="String" Description="操作" />
      <Column Name="LinkID" DataType="Int32" Description="链接" />
      <Column Name="UserName" DataType="String" Description="用户名" />
      <Column Name="CreateUserID" DataType="Int32" Description="用户编号" />
      <Column Name="CreateIP" DataType="String" Description="IP地址" />
      <Column Name="CreateTime" DataType="DateTime" Description="时间" />
      <Column Name="Remark" DataType="String" Length="500" Description="详细信息" />
    </Columns>
    <Indexes>
      <Index Columns="Category" />
      <Index Columns="CreateUserID" />
      <Index Columns="CreateTime" />
    </Indexes>
  </Table>
  <Table Name="UserOnline" Description="用户在线" ConnName="Log">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
      <Column Name="UserID" DataType="Int32" Description="用户" />
      <Column Name="Name" DataType="String" Master="True" Description="名称" />
      <Column Name="SessionID" DataType="String" Description="会话。Web的SessionID或Server的会话编号" />
      <Column Name="Times" DataType="Int32" Description="次数" />
      <Column Name="Page" DataType="String" Description="页面" />
      <Column Name="Status" DataType="String" Length="200" Description="状态" />
      <Column Name="OnlineTime" DataType="Int32" Description="在线时间。本次在线总时间,秒" />
      <Column Name="CreateIP" DataType="String" Description="创建地址" />
      <Column Name="CreateTime" DataType="DateTime" Description="创建时间" />
      <Column Name="UpdateTime" DataType="DateTime" Description="修改时间" />
    </Columns>
    <Indexes>
      <Index Columns="UserID" />
      <Index Columns="SessionID" />
      <Index Columns="CreateTime" />
    </Indexes>
  </Table>
  <Table Name="VisitStat" Description="访问统计" ConnName="Log">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
      <Column Name="Level" DataType="Int32" Description="层级" Type="XCode.Statistics.StatLevels" />
      <Column Name="Time" DataType="DateTime" Description="时间" />
      <Column Name="Page" DataType="String" Nullable="False" Description="页面" />
      <Column Name="Title" DataType="String" Master="True" Description="标题" />
      <Column Name="Times" DataType="Int32" Description="次数" />
      <Column Name="Users" DataType="Int32" Description="用户" />
      <Column Name="IPs" DataType="Int32" Description="IP" />
      <Column Name="Error" DataType="Int32" Description="错误" />
      <Column Name="Cost" DataType="Int32" Description="耗时。毫秒" />
      <Column Name="MaxCost" DataType="Int32" Description="最大耗时。毫秒" />
      <Column Name="CreateTime" DataType="DateTime" Description="创建时间" />
      <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" />
      <Column Name="Remark" DataType="String" Length="500" Description="详细信息" />
    </Columns>
    <Indexes>
      <Index Columns="Page,Level,Time" Unique="True" />
      <Index Columns="Level,Time" />
    </Indexes>
  </Table>
</Tables>

头部属性解释:

  • Output,输出目录
  • NameSpace,命名空间
  • ConnName,连接名,对应app.config/web.config中connectionStrings下的name
  • BaseEntity,基类,默认Entity,同一个子模块也可以共用自己的泛型基类EntityBase

Tables中的这些属性对本模型文件所有Table有效,各个Table上也可以指定这些属性,以覆盖全局设置

Table.Name 就是生成的实体类类名,如果实际表名不同,可用TableName指定表名。

Column.Name就是生成的实体类属性名,如果实际字段名不同,可用ColumnName指定字段名。

字符串长度Length默认50,不建议小于50,-1表示最大ntext

其它字段不建议设置长度,特别不建议给Double和Decimal设置精度

字段Column只需要DataType指定.Net类型即可,反向工程会根据使用数据库的不同而映射到不同数据库类型。

如果不喜欢XCode推荐的数据库类型,可在Column中通过RawType指定原始数据库类型。

Column支持Type指定枚举类型,建议是带命名空间的全名。例如上面User表中Sex类型的字段就是枚举SexKinds

Indexes内放置该表所有索引。

每一行Index为一组索引,Columns内指定索引所需要的字段(注意先后顺序),Unique指定是否唯一索引。

!!XCode反向工程支持自动创建或删除索引。

可以通过码神工具/建模工具,从数据库中导出数据表对应的模型文件;

也可以编码通过DAL.Export导出模型文件;

魔方的系统管理数据库页面,也可以导出模型文件;

数据名字规范

模型文件的Table名将会生成实体类类名,Column字段名将会生成实体类属性名,因此命名规范很重要。

  1. 名称必须使用通俗易懂的英文单词全拼,常用的缩略词(如ID)除外
  2. 采用大驼峰命名,每个单词首字母大写,其它小写,类型属性名符合.Net规范
  3. 名称必须简洁明了,不要加多余的前缀(如表名前加tbl),字段名也不要加表名前缀
  4. 不得使用SQL关键字或C#关键字作为表名或字段名
  5. 使用数据库常用类型,如Int32和String,大文本长度-1
  6. 给表和字段加上说明,作为文件名,以及生成代码的注释
  7. 每张表必须有唯一主键字段(建议自增ID)
  8. 主从表中,从表加关联字段(主表名+主表主键名)。XCode会识别为主从关系,生成扩展属性
  9. 每张表设置好索引,注意是否唯一。XCode会识别为索引,生成扩展查询

数据类型规范

模型文件设计要求开发者有一点数据库基础,至少要能明确表、字段和索引的概念。

然而要求又远比数据库要低得多,因为咱们推崇极致简单的原则。

类型 数据库类型 推荐 备注
Int32 整数 int 强烈 优先
String 字符串 nvarchar(50) 强烈 默认变长50
DateTime 时间日期 datetime 强烈 不建议纯日期或时间
Boolean 布尔型 bit 强烈 MySql中建议tinyint替代枚举来实现布尔型
Int64 长整型 long 强烈 有可能超过21亿的整数,选长整型
Decimal 十进制 money 慎用 高精度货币型时采用
Double 双精度 double 慎用 特别慎用,避免浮点运算导致精度丢失
Int16 short 禁用 由Int32替代
Byte tinyint 禁用 由Int32替代
Single single 禁用 由Double替代

常用字段推荐

为了便于开发,XCode默认优待以下字段:

<Column Name="CreateUser" DataType="String" Description="创建者" />
<Column Name="CreateUserID" DataType="Int32" Description="创建者" />
<Column Name="CreateTime" DataType="DateTime" Description="创建时间" />
<Column Name="CreateIP" DataType="String" Description="创建地址" />
<Column Name="UpdateUser" DataType="String" Description="更新者" />
<Column Name="UpdateUserID" DataType="Int32" Description="更新者" />
<Column Name="UpdateTime" DataType="DateTime" Description="更新时间" />
<Column Name="UpdateIP" DataType="String" Description="更新地址" />

时间组CreateTime/UpdateTime字段对应TimeModule,在新增或更新时自动赋值;

IP组CreateIP/UpdateIP字段对应IPModule,在Web新增或更新时自动赋值;

用户组CreateUser(ID)/UpdateUser(ID)字段对应UserModule,在Web新增或更新时字段赋值;

一句话:用了这些字段,在Insert时自动给CreateAbc赋值,在Update时自动给UpdateAbc赋值!

最佳实践

多年开发经验积累,得到一些技巧:

  • 珍藏自己最得意的模型文件model.xml,如有需要也可以珍藏build.tt
  • 新建数据类库项目时,为每个模块建立子目录,其中放入build.tt+model.xml
  • 修改model.xml的命名空间和连接名等设置
  • 也可以根据项目性质,找到最接近的项目,拷贝其model.xml作为新项目模版
  • 修改新项目模块的model.xml,根据设计逐步完善数据表和索引
  • 根据团队开发习惯,不同表会有很多类似字段设计可重复使用,例如上面的常用字段推荐
  • 在项目推进过程中,如有增加字段,XCode反向工程会自动给数据库加上,无需人工处理
  • 开发阶段配置各个连接为本地SQLite数据库,若无配置,默认就是SQLite
  • 发布生产环境时,再配置连接字符串为实际数据库,如MySql、Oracle、SqlServer,XCode确保跨数据库兼容。