EFCore 通过实体Model生成创建SQL Server数据库表脚本
在我们的项目中经常采用Model First这种方式先来设计数据库Model,然后通过Migration来生成数据库表结构,有些时候我们需要动态通过实体Model来创建数据库的表结构,特别是在创建像临时表这一类型的时候,我们直接通过代码来进行创建就可以了不用通过创建实体然后迁移这种方式来进行,其实原理也很简单就是通过遍历当前Model然后获取每一个属性并以此来生成部分创建脚本,然后将这些创建的脚本拼接成一个完整的脚本到数据库中去执行就可以了,只不过这里有一些需要注意的地方,下面我们来通过代码来一步步分析怎么进行这些代码规范编写以及需要注意些什么问题。
一 代码分析
/// <summary>
/// Model 生成数据库表脚本
/// </summary>
public class TableGenerator : ITableGenerator {
private static Dictionary<Type, string> DataMapper {
get {
var dataMapper = new Dictionary<Type, string> {
{typeof(int), "NUMBER(10) NOT NULL"},
{typeof(int?), "NUMBER(10)"},
{typeof(string), "VARCHAR2({0} CHAR)"},
{typeof(bool), "NUMBER(1)"},
{typeof(DateTime), "DATE"},
{typeof(DateTime?), "DATE"},
{typeof(float), "FLOAT"},
{typeof(float?), "FLOAT"},
{typeof(decimal), "DECIMAL(16,4)"},
{typeof(decimal?), "DECIMAL(16,4)"},
{typeof(Guid), "CHAR(36)"},
{typeof(Guid?), "CHAR(36)"}
}; return dataMapper;
}
} private readonly List<KeyValuePair<string, PropertyInfo>> _fields = new List<KeyValuePair<string, PropertyInfo>>(); /// <summary>
///
/// </summary>
private string _tableName; /// <summary>
///
/// </summary>
/// <returns></returns>
private string GetTableName(MemberInfo entityType) {
if (_tableName != null)
return _tableName;
var prefix = entityType.GetCustomAttribute<TempTableAttribute>() != null ? "#" : string.Empty;
return _tableName = $"{prefix}{entityType.GetCustomAttribute<TableAttribute>()?.Name ?? entityType.Name}";
} /// <summary>
/// 生成创建表的脚本
/// </summary>
/// <returns></returns>
public string GenerateTableScript(Type entityType) {
if (entityType == null)
throw new ArgumentNullException(nameof(entityType)); GenerateFields(entityType); const int DefaultColumnLength = 500;
var script = new StringBuilder(); script.AppendLine($"CREATE TABLE {GetTableName(entityType)} (");
foreach (var (propName, propertyInfo) in _fields) {
if (!DataMapper.ContainsKey(propertyInfo.PropertyType))
throw new NotSupportedException($"尚不支持 {propertyInfo.PropertyType}, 请联系开发人员.");
if (propertyInfo.PropertyType == typeof(string)) {
var maxLengthAttribute = propertyInfo.GetCustomAttribute<MaxLengthAttribute>();
script.Append($"\t {propName} {string.Format(DataMapper[propertyInfo.PropertyType], maxLengthAttribute?.Length ?? DefaultColumnLength)}");
if (propertyInfo.GetCustomAttribute<RequiredAttribute>() != null)
script.Append(" NOT NULL");
script.AppendLine(",");
} else {
script.AppendLine($"\t {propName} {DataMapper[propertyInfo.PropertyType]},");
}
} script.Remove(script.Length - 1, 1); script.AppendLine(")"); return script.ToString();
} private void GenerateFields(Type entityType) {
foreach (var p in entityType.GetProperties()) {
if (p.GetCustomAttribute<NotMappedAttribute>() != null)
continue;
var columnName = p.GetCustomAttribute<ColumnAttribute>()?.Name ?? p.Name;
var field = new KeyValuePair<string, PropertyInfo>(columnName, p);
_fields.Add(field);
}
}
}
这里的TableGenerator继承自接口ITableGenerator,在这个接口内部只定义了一个 string GenerateTableScript(Type entityType) 方法。
/// <summary>
/// Model 生成数据库表脚本
/// </summary>
public interface ITableGenerator {
/// <summary>
/// 生成创建表的脚本
/// </summary>
/// <returns></returns>
string GenerateTableScript(Type entityType);
}
这里我们来一步步分析这些部分的含义,这个里面DataMapper主要是用来定义一些C#基础数据类型和数据库生成脚本之间的映射关系。
1 GetTableName
接下来我们看看GetTableName这个函数,这里首先来当前Model是否定义了TempTableAttribute,这个看名字就清楚了就是用来定义当前Model是否是用来生成一张临时表的。
/// <summary>
/// 是否临时表, 仅限 Dapper 生成 数据库表结构时使用
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class TempTableAttribute : Attribute { }
具体我们来看看怎样在实体Model中定义TempTableAttribute这个自定义属性。
[TempTable]
class StringTable {
public string DefaultString { get; set; }
[MaxLength(30)]
public string LengthString { get; set; }
[Required]
public string NotNullString { get; set; }
}
就像这样定义的话,我们就知道当前Model会生成一张SQL Server的临时表。
当然如果是生成临时表,则会在生成的表名称前面加一个‘#’标志,在这段代码中我们还会去判断当前实体是否定义了TableAttribute,如果定义过就去取这个TableAttribute的名称,否则就去当前Model的名称,这里也举一个实例。
[Table("Test")]
class IntTable {
public int IntProperty { get; set; }
public int? NullableIntProperty { get; set; }
}
这样我们通过代码创建的数据库名称就是Test啦。
2 GenerateFields
这个主要是用来一个个读取Model中的属性,并将每一个实体属性整理成一个KeyValuePair<string, PropertyInfo>的对象从而方便最后一步来生成整个表完整的脚本,这里也有些内容需要注意,如果当前属性定义了NotMappedAttribute标签,那么我们可以直接跳过当前属性,另外还需要注意的地方就是当前属性的名称首先看当前属性是否定义了ColumnAttribute的如果定义了,那么数据库中字段名称就取自ColumnAttribute定义的名称,否则才是取自当前属性的名称,通过这样一步操作我们就能够将所有的属性读取到一个自定义的数据结构List<KeyValuePair<string, PropertyInfo>>里面去了。
3 GenerateTableScript
有了前面的两步准备工作,后面就是进入到生成整个创建表脚本的部分了,其实这里也比较简单,就是通过循环来一个个生成每一个属性对应的脚本,然后通过StringBuilder来拼接到一起形成一个完整的整体。这里面有一点需要我们注意的地方就是当前字段是否可为空还取决于当前属性是否定义过RequiredAttribute标签,如果定义过那么就需要在创建的脚本后面添加Not Null,最后一个重点就是对于string类型的属性我们需要读取其定义的MaxLength属性从而确定数据库中的字段长度,如果没有定义则取默认长度500。
当然一个完整的代码怎么能少得了单元测试呢?下面我们来看看单元测试。
二 单元测试
public class SqlServerTableGenerator_Tests {
[Table("Test")]
class IntTable {
public int IntProperty { get; set; }
public int? NullableIntProperty { get; set; }
} [Fact]
public void GenerateTableScript_Int_Number10() {
// Act
var sql = new TableGenerator().GenerateTableScript(typeof(IntTable));
// Assert
sql.ShouldContain("IntProperty NUMBER(10) NOT NULL");
sql.ShouldContain("NullableIntProperty NUMBER(10)");
} [Fact]
public void GenerateTableScript_TestTableName_Test() {
// Act
var sql = new TableGenerator().GenerateTableScript(typeof(IntTable));
// Assert
sql.ShouldContain("CREATE TABLE Test");
} [TempTable]
class StringTable {
public string DefaultString { get; set; }
[MaxLength(30)]
public string LengthString { get; set; }
[Required]
public string NotNullString { get; set; }
} [Fact]
public void GenerateTableScript_TempTable_TableNameWithSharp() {
// Act
var sql = new TableGenerator().GenerateTableScript(typeof(StringTable));
// Assert
sql.ShouldContain("Create Table #StringTable");
} [Fact]
public void GenerateTableScript_String_Varchar() {
// Act
var sql = new TableGenerator().GenerateTableScript(typeof(StringTable));
// Assert
sql.ShouldContain("DefaultString VARCHAR2(500 CHAR)");
sql.ShouldContain("LengthString VARCHAR2(30 CHAR)");
sql.ShouldContain("NotNullString VARCHAR2(500 CHAR) NOT NULL");
} class ColumnTable {
[Column("Test")]
public int IntProperty { get; set; }
[NotMapped]
public int Ingored {get; set; }
} [Fact]
public void GenerateTableScript_ColumnName_NewName() {
// Act
var sql = new TableGenerator().GenerateTableScript(typeof(ColumnTable));
// Assert
sql.ShouldContain("Test NUMBER(10) NOT NULL");
} [Fact]
public void GenerateTableScript_NotMapped_Ignore() {
// Act
var sql = new TableGenerator().GenerateTableScript(typeof(ColumnTable));
// Assert
sql.ShouldNotContain("Ingored NUMBER(10) NOT NULL");
} class NotSupportedTable {
public dynamic Ingored {get; set; }
} [Fact]
public void GenerateTableScript_NotSupported_ThrowException() {
// Act
Assert.Throws<NotSupportedException>(() => {
new TableGenerator().GenerateTableScript(typeof(NotSupportedTable));
});
}
}
最后我们来看看最终生成的创建表的脚本。
1 定义过TableAttribute的脚本。
CREATE TABLE Test (
IntProperty NUMBER(10) NOT NULL,
NullableIntProperty NUMBER(10),
)
2 生成的临时表的脚本。
CREATE TABLE #StringTable (
DefaultString VARCHAR2(500 CHAR),
LengthString VARCHAR2(30 CHAR),
NotNullString VARCHAR2(500 CHAR) NOT NULL,
)
通过这种方式我们就能够在代码中去动态生成数据库表结构了。
EFCore 通过实体Model生成创建SQL Server数据库表脚本的更多相关文章
- EF Core中,通过实体类向SQL Server数据库表中插入数据后,实体对象是如何得到数据库表中的默认值的
我们使用EF Core的实体类向SQL Server数据库表中插入数据后,如果数据库表中有自增列或默认值列,那么EF Core的实体对象也会返回插入到数据库表中的默认值. 下面我们通过例子来展示,EF ...
- 修改SQL Server数据库表的创建时间最简单最直接有效的方法
说明:这篇文章是几年前我发布在网易博客当中的原创文章,但由于网易博客现在要停止运营了,所以我就把这篇文章搬了过来,因为这种操作方式是通用的,即使是对现在最新的SQL Server数据库里面的操作也是一 ...
- 千万级SQL Server数据库表分区的实现
千万级SQL Server数据库表分区的实现 2010-09-10 13:37 佚名 数据库 字号:T | T 一般在千万级的数据压力下,分区是一种比较好的提升性能方法.本文将介绍SQL Server ...
- SQL SERVER 数据库表同步复制 笔记
SQL SERVER 数据库表同步复制 笔记 同步复制可运行在不同版本的SQL Server服务之间 环境模拟需要两台数据库192.168.1.1(发布),192.168.1.10(订阅) 1.在发布 ...
- SQL Server 数据库表的管理
上一篇文章简单梳理了一下SQL Server数据库的安装和基本操作,这篇文章主要讲述一下数据库表的管理 一.数据库的创建 有关数据库的创建有两种方式,一种是通过视图创建,第二种就是通过T-SQL语句来 ...
- 将文件导入到SQL server数据库表中的字段中
一.在要执行的sql server数据库a中执行如下脚本,创建存储过程sp_textcopy /* 将二进制文件导入.导出到数据库相应字段列中 */ CREATE PROCEDURE sp_textc ...
- 创建SQL Server数据库集群的经历
自己尝试安装SQL Server集群和配置AlwaysOn可用性组,服务器系统是Windows Server 2012 R2,SQL Server是2014企业版,我的环境是一台服务器,然后用Hype ...
- SQL Server 数据库巡检脚本
--1.查看数据库版本信息 select @@version --2.查看所有数据库名称及大小 exec sp_helpdb --3.查看数据库所在机器的操作系统参数 exec master..xp_ ...
- 获取sql server数据库表结构
if exists (select 1 from sysobjects where name = 'sysproperties'and xtype = 'V')begin DROP VIEW s ...
随机推荐
- mac环境使用python处理protobuf
安装 brew install protobuf 然后再安装protobuf需要的依赖 brew install autoconf automake libtool 验证是否安装成功 protoc – ...
- 2018-2019-2 网络对抗技术 20165322 Exp7 网络欺诈防范
2018-2019-2 网络对抗技术 20165322 Exp7 网络欺诈防范 目录 实验原理 实验内容与步骤 简单应用SET工具建立冒名网站 ettercap DNS spoof 结合应用两种技术, ...
- Oracle表空间 与 分页
目录 Oracle表空间 表空间的构建以及数据文件的挂接 创建一个用户,并指定专有的永久表空间和临时表空间 伪列 分页 Oracle表空间 一个表空间可以包含1至n个数据文件 一个数据文件必然属于某个 ...
- 小福bbs-冲刺日志(第四天)
[小福bbs-冲刺日志(第四天)] 这个作业属于哪个课程 班级链接 这个作业要求在哪里 作业要求的链接 团队名称 小福bbs 这个作业的目标 两个前端完成15个界面 作业的正文 小福bbs-冲刺日志( ...
- 文献阅读 | Epigenetics in ENS development and Hirschsprung disease
系列篇: Epigenetics in ENS development and Hirschsprung disease - Review Epigenetic Mechanisms in Hirsc ...
- 降维算法整理--- PCA、KPCA、LDA、MDS、LLE 等
转自github: https://github.com/heucoder/dimensionality_reduction_alo_codes 网上关于各种降维算法的资料参差不齐,同时大部分不提供源 ...
- 扩展和嵌入 Python 解释器 用 C 或 C++ 编写模块以使用新模块来扩展 Python 解释器的功能 定义新的函数\对象类型\方法。 将 Python 解释器嵌入到另一个应用程序中
// https://python3-cookbook.readthedocs.io/zh_CN/latest/c15/p02_write_simple_c_extension_module.html ...
- Gradle vs. Maven: Performance, Compatibility, Speed, & Builds
Gradle vs. Maven: Performance, Compatibility, Speed, & Buildshttps://stackify.com/gradle-vs-mave ...
- Windows系统CPU和内存状态实时查询(Java)
一.背景 需要查询Windows服务器的CPU和内存状态. Linux系统查询CPU和内存状态很简单,一个top命令搞定,Windows就稍微麻烦一些了. 经过资料查找,发现jdk目前不能直接查询系统 ...
- 为什么说基于TCP的移动端IM仍然需要心跳保活?(转)
源:https://segmentfault.com/a/1190000006832547 为什么说基于TCP的移动端IM仍然需要心跳保活?