在我们的项目中经常采用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) 方法。