在我们的项目中经常采用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>()"; } /// <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"); 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>()"htmlcode">/// <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"htmlcode">public class SqlServerTableGenerator_Tests { [Table("Test")] class IntTable { public int IntProperty { get; set; } public int"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数据库表脚本的详细内容,更多关于EFCore 创建SQL Server数据库表脚本的资料请关注其它相关文章!