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生成多个实体类文件!
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内部引用)
直接下载模版
可以直接下载 https://x.newlifex.com/XCode_BuildModel.zip ,确保build.tt/build_netcore.tt文件属性中的自定义工具为 TextTemplatingFileGenerator 。
解压拷贝model.xml和build.tt(netcore项目选build_netcore.tt)到需要放置实体类的目标目录。
手工编写模版(解决.NET5.0报错)
.tt文件本质上就是VisualStudio的T4模版,可以在vs中新建一个,并确保其*文件属性中的自定义工具为 TextTemplatingFileGenerator 。
build.tt和model.xml的文件名都可以随意修改,build.tt默认会自动查找当前目录的第一个xml文件来使用。或者修改build.tt文件内容,强制指定文件名
<#@ template language="C#" hostSpecific="true" debug="true" #>
<#@ assembly name="netstandard" #>
<#@ assembly name="$(SolutionDir)\DLL\NewLife.Core.dll" #>
<#@ assembly name="$(SolutionDir)\DLL\XCode.dll" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="XCode.Code" #>
<#@ output extension=".log" #>
<#
// 设置当前工作目录
PathHelper.BasePath = Host.ResolvePath(".");
// 导入模型文件并生成实体类,模型文件、输出目录、命名空间、连接名、中文文件名、表名字段名大小写
//EntityBuilder.Build(String xmlFile = null, String output = null, String nameSpace = null, String connName = null, Boolean? chineseFileName = true,Boolean? nameIgnoreCase = null);
EntityBuilder.Build();
//var tables = DAL.ImportFrom("Company.Project.xml");
//EntityBuilder.Build(tables);
#>
在.NETCore3.1/.NET5.0项目使用XCode的build_netcore.tt模板生成时,可能会报以下错误(未能找到文件):
或者报以下错误(正在编译转换:类型”Object”在未引用的程序集中定义。必须添加对程序集的引用):
这是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,加入开发数据库的连接信息)
选择并连接数据库,可以看到该库的所有表信息,此时只需要“导出模型”,即可得到该库的model.xml模型文件。
(码神工具在早期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字段名将会生成实体类属性名,因此命名规范很重要。
- 名称必须使用通俗易懂的英文单词全拼,常用的缩略词(如ID)除外
- 采用大驼峰命名,每个单词首字母大写,其它小写,类型属性名符合.Net规范
- 名称必须简洁明了,不要加多余的前缀(如表名前加tbl),字段名也不要加表名前缀
- 不得使用SQL关键字或C#关键字作为表名或字段名
- 使用数据库常用类型,如Int32和String,大文本长度-1
- 给表和字段加上说明,作为文件名,以及生成代码的注释
- 每张表必须有唯一主键字段(建议自增ID)
- 主从表中,从表加关联字段(主表名+主表主键名)。XCode会识别为主从关系,生成扩展属性
- 每张表设置好索引,注意是否唯一。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确保跨数据库兼容。