在我们的项目中经常采用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数据库表脚本的更多相关文章

  1. EF Core中,通过实体类向SQL Server数据库表中插入数据后,实体对象是如何得到数据库表中的默认值的

    我们使用EF Core的实体类向SQL Server数据库表中插入数据后,如果数据库表中有自增列或默认值列,那么EF Core的实体对象也会返回插入到数据库表中的默认值. 下面我们通过例子来展示,EF ...

  2. 修改SQL Server数据库表的创建时间最简单最直接有效的方法

    说明:这篇文章是几年前我发布在网易博客当中的原创文章,但由于网易博客现在要停止运营了,所以我就把这篇文章搬了过来,因为这种操作方式是通用的,即使是对现在最新的SQL Server数据库里面的操作也是一 ...

  3. 千万级SQL Server数据库表分区的实现

    千万级SQL Server数据库表分区的实现 2010-09-10 13:37 佚名 数据库 字号:T | T 一般在千万级的数据压力下,分区是一种比较好的提升性能方法.本文将介绍SQL Server ...

  4. SQL SERVER 数据库表同步复制 笔记

    SQL SERVER 数据库表同步复制 笔记 同步复制可运行在不同版本的SQL Server服务之间 环境模拟需要两台数据库192.168.1.1(发布),192.168.1.10(订阅) 1.在发布 ...

  5. SQL Server 数据库表的管理

    上一篇文章简单梳理了一下SQL Server数据库的安装和基本操作,这篇文章主要讲述一下数据库表的管理 一.数据库的创建 有关数据库的创建有两种方式,一种是通过视图创建,第二种就是通过T-SQL语句来 ...

  6. 将文件导入到SQL server数据库表中的字段中

    一.在要执行的sql server数据库a中执行如下脚本,创建存储过程sp_textcopy /* 将二进制文件导入.导出到数据库相应字段列中 */ CREATE PROCEDURE sp_textc ...

  7. 创建SQL Server数据库集群的经历

    自己尝试安装SQL Server集群和配置AlwaysOn可用性组,服务器系统是Windows Server 2012 R2,SQL Server是2014企业版,我的环境是一台服务器,然后用Hype ...

  8. SQL Server 数据库巡检脚本

    --1.查看数据库版本信息 select @@version --2.查看所有数据库名称及大小 exec sp_helpdb --3.查看数据库所在机器的操作系统参数 exec master..xp_ ...

  9. 获取sql server数据库表结构

    if exists (select 1 from sysobjects where name = 'sysproperties'and xtype = 'V')begin    DROP VIEW s ...

随机推荐

  1. Vue.js中 watch的理解以及深度监听

    如代码: <div> <p>FullName: {{fullName}}</p> <p>FirstName: <input type=" ...

  2. kubernetes架构和组件

    一.Kubernetes整体架构 Kubernetes属于主从分布式架构,主要由Master Node和Worker Node组成,以及包括客户端命令行工具kubectl和其它附加项. Master ...

  3. php手记之08-tp5中间件

    01-创建中间件 php think make:middleware 中间件的名称 这个指令会 application/http/middleware目录下面生成一个中间件文件. 02-注册中间件三种 ...

  4. HearthBuddy修改系统时间

    将以下代码保存在.bat文件,然后用管理员权限运行 pushd "%~dp0" #下面修改时间,根据操作系统的语言不同,会有不同的格式,比如2019-10-26date 10/26 ...

  5. Windows 开始 运行中所打开的默认程序以及优先级

    Windows 开始 运行中所打开的默认程序以及优先级 Default app/softwares and priority for Windows/start/run 商务合作,科技咨询,版权转让: ...

  6. 002 elasticsearch中的一些概念

    在本文中,主要是ES7中的核心概念. ElasticSearch是一个实时分布式开源全文搜索和分析引擎.它可以从RESTful网络服务接口访问,并使用无模式JSON (JavaScript对象符号)文 ...

  7. flutter 中文件工具类

    添加依赖: path_provider: ^0.5.0+1 import 'dart:convert'; import 'dart:io'; import 'package:path_provider ...

  8. Vue CLI3和Vue CLI2环境搭建

    关于 Vue CLI 旧版本的安装以及创建项目 1.搭建 vue 的开发环境 ,安装 vue 的脚手架工具 官方命令行工具 npm install --global vue-cli / cnpm in ...

  9. Oracle 自动生成的视图VM_NSO_1

    作者:Jerry 有时候优化sql的时候,在执行计划中看到有VM_NSO_X的视图,在Oracle定义中,可以吧NSO理解为nested subquery optimizing,功能就是把in转换为j ...

  10. doris: shell invoke .sql script for doris and passing values for parameters in sql script.

    1. background in most cases, we want to execute sql script  in doris  routinely. using azkaban, to l ...