Fork me on GitHub

CSRobot中的gen命令

  CSRobot  https://github.com/axzxs2001/CSRobot

  gen命令是用来从数据库,生成实体类,前一篇文章说到要实现两个接口,其中一个是从数据库中查询出库,表,字段的信息,转成实体类。

 

首先说一下gen命令的属性  

 csrobot gen [options]

命令参数选项:

   
--dbtype 数据库类型,必填,例如:--dbtype=mysql,--dbtype=mssql,--dbtype=postgressql
--table 指定数据库表名生成实体类,缺省默认全部库表
--out  生成实体类的路径,缺省默认当前路径
--tep 生成实体类的模板,可以是内置的模板cs,或指定本地路径,或指定url,生成文件的扩展名与指定的模板扩展名匹配。缺省默认cs内置模板,例如:--tep=/usr/abc/bcd.cs;--tep=https://github.com/abc/bcd.cs;--tep=cs
--host  连接数据所在主机,如果缺少此项,会查找当前目录或子目录中的是否存在appsettings.json配置文件,并取配置文件下的ConnectionStrings节点的第一个子节点的值作为连接字符串
--db  数据库名称,如果缺少此项,会查找当前目录或子目录中的是否存在appsettings.json配置文件,并取配置文件下的ConnectionStrings节点的第一个子节点的值作为连接字符串
--user 数据库用户名,如果缺少此项,会查找当前目录或子目录中的是否存在appsettings.json配置文件,并取配置文件下的ConnectionStrings节点的第一个子节点的值作为连接字符串
--pwd 数据库密码,如果缺少此项,会查找当前目录或子目录中的是否存在appsettings.json配置文件,并取配置文件下的ConnectionStrings节点的第一个子节点的值作为连接字符串
--port 数据库端口号,如果缺少此项,会查找当前目录或子目录中的是否存在appsettings.json配置文件,并取配置文件下的ConnectionStrings节点的第一个子节点的值作为连接字符串

 

 

ITraverser实现

  ITraverser接口实现的步骤是这样的:

  ITraverser接口->Traverser抽象类->MySqlTraverser实现类

 

  ITraverser接口

  /// <summary>
    /// 完成从数据库生成数据库结构实体
    /// </summary>
    public interface ITraverser
    {
        DataBase Traverse();
    }

  初步规划是实现mysql,mssql,postgres三种类型的实的信息获取,接下来实现了一个Traverser类抽象类,来处理一些公共的验证,比如命令中缺少必填属性的处理。

  Traverser类

 public abstract class Traverser : ITraverser
    {
        protected bool IsExistOption { get; set; } = true;
        public Traverser(CommandOptions options)
        {
            if (!options.ContainsKey("--host"))
            {
                IsExistOption = false;
                Console.WriteLine("缺少 --host");
            }
            if (!options.ContainsKey("--db"))
            {
                IsExistOption = false;
                Console.WriteLine("缺少 --db");
            }
            if (!options.ContainsKey("--user"))
            {
                IsExistOption = false;
                Console.WriteLine("缺少 --user");
            }
            if (!options.ContainsKey("--pwd"))
            {
                IsExistOption = false;
                Console.WriteLine("缺少 --pwd");
            }
        }
        public abstract DataBase Traverse();       
    }

  接下来是MySqlTraverser的实现。

  MySqlTraverser类

  public class MySqlTraverser : Traverser
    {
        MySqlConnectionStringBuilder _connectionStringBuilder;
        public MySqlTraverser(CommandOptions options) : base(options)
        {
            if (IsExistOption)
            {
                _connectionStringBuilder = new MySqlConnectionStringBuilder()
                {
                    Server = options["--host"],
                    Database = options["--db"],
                    UserID = options["--user"],
                    Password = options["--pwd"],
                    Port = options.ContainsKey("--port") ? uint.Parse(options["--port"]) : 3306,
                };
            }
            else
            {
                var connectionString = Common.GetConnectionString();
                if (string.IsNullOrEmpty(connectionString))
                {
                    Console.WriteLine("本地配置文件中找不到数据库连接字符串");
                }
                _connectionStringBuilder = new MySqlConnectionStringBuilder(connectionString);
            }
        }
        public override DataBase Traverse()
        {
            return GetDataBase();
        }
        DataBase GetDataBase()
        {
            var dataBase = new DataBase()
            {
                DataBaseName = _connectionStringBuilder.Database
            };

            using (var con = new MySqlConnection(_connectionStringBuilder.ConnectionString))
            {
                var sql = @$"select table_name as tablename,table_comment as tabledescribe from information_schema.tables where table_schema='{_connectionStringBuilder.Database}' and table_type='BASE TABLE';";
                var cmd = new MySqlCommand(sql, con);
                con.Open();
                var reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    var table = new Table();
                    table.TableName = reader.GetFieldValue<string>("tablename");
                    table.TableDescribe = reader.GetFieldValue<string>("tabledescribe");
                    dataBase.Tables.Add(table);
                }
                con.Close();
            }
            GetFields(dataBase);
            return dataBase;
        }

        void GetFields(DataBase dataBase)
        {
            foreach (var table in dataBase.Tables)
            {
                var sql = @$"select character_maximum_length as fieldsize,column_name as fieldname,data_type as dbtype,column_comment as fielddescribe from information_schema.columns where table_name = '{table.TableName}' ";
                using (var con = new MySqlConnection(_connectionStringBuilder.ConnectionString))
                {
                    var cmd = new MySqlCommand(sql, con);
                    con.Open();
                    var reader = cmd.ExecuteReader();
                    while (reader.Read())
                    {
                        var field = new Field();
                        field.FieldName = reader.GetFieldValue<string>("fieldname");
                        field.FieldDescribe = reader.GetFieldValue<string>("fielddescribe");
                        field.DBType = reader.GetFieldValue<string>("dbtype");
                        var size = reader.GetFieldValue<object>("fieldsize");
                        if (size != DBNull.Value)
                        {
                            field.FieldSize = Convert.ToInt64(size);
                        }
                        table.Fields.Add(field);
                    }
                }
            }
        }
    }

IBuilder实现

 

IBuilder接口

 /// <summary>
    /// 完成对应编程语言实体类生成
    /// </summary>
    public interface IBuilder
    {
        void Build(DataBase database, CommandOptions options);
    }

CSharpBuilder实现类

using CSRobot.GenerateEntityTools.Entity;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;

namespace CSRobot.GenerateEntityTools.Builders
{
    public class CSharpBuilder : IBuilder
    {
        public void Build(DataBase database, CommandOptions options)
        {
            //取输出路径
            var basePath = GetOut(options, database.DataBaseName);
            var template = GetTamplate(options);
            //生成独立的表
            if (options.ContainsKey("--table"))
            {
                var table = database.Tables.SingleOrDefault(s => s.TableName == options["--table"]);
                if (table != null)
                {
                    var codeString = GetCodeString(database.DataBaseName, table, template.Template);
                    File.WriteAllText($"{basePath}/{table.TableName}{template.Extension}", codeString.ToString(), Encoding.UTF8);
                }
                else
                {
                    throw new ApplicationException($"找不到{options["--table"]}表");
                }
            }
            else
            {
                //生成所有表实体类
                foreach (var table in database.Tables)
                {
                    var filePath = $"{basePath}/{table.TableName}{template.Extension}";
                    if (File.Exists(filePath))
                    {
                        Console.WriteLine($"{filePath}已存在,是否覆盖?Y为覆盖,N为不覆盖");
                        if (Console.ReadLine().ToLower() == "y")
                        {
                            var codeString = GetCodeString(database.DataBaseName, table, template.Template);
                            File.WriteAllText(filePath, codeString.ToString(), Encoding.UTF8);
                        }
                    }
                    else
                    {
                        var codeString = GetCodeString(database.DataBaseName, table, template.Template);
                        File.WriteAllText(filePath, codeString.ToString(), Encoding.UTF8);
                    }
                }
            }
        }
        /// <summary>
        /// 模板替换
        /// </summary>
        /// <param name="dataBaseName"></param>
        /// <param name="table"></param>
        /// <param name="template"></param>
        /// <returns></returns>
        private string GetCodeString(string dataBaseName, Table table, string template)
        {
            template = template.Replace("${DataBaseName}", dataBaseName);
            template = template.Replace("${TableDescribe}", table.TableDescribe);
            template = template.Replace("${TableName}", table.TableName);
            var match = Regex.Match(template, @"(?<=\$\{Fields\})[\w\W]+(?=\$\{Fields\})");
            if (match.Success)
            {
                var reg = new Regex(@"(?<=\$\{Fields\})[\w\W]+(?=\$\{Fields\})");
                return reg.Replace(template, GetFieldString(table, match.Value.Trim(' ')).Trim()).Replace("${Fields}", "");
            }
            return template;
        }
        /// <summary>
        /// 处理属性
        /// </summary>
        /// <param name="table"></param>
        /// <param name="fieldTamplate"></param>
        /// <returns></returns>
        private string GetFieldString(Table table, string fieldTamplate)
        {
            var fields = new StringBuilder();
            foreach (var field in table.Fields)
            {
                //把模板分成行,分别处理
                var lines = fieldTamplate.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

                var newFieldTamplate = new StringBuilder();
                foreach (var line in lines)
                {
                    var newLine = line;
                    if (newLine.Trim().StartsWith("$?{"))
                    {
                        var match = Regex.Match(newLine, @"(?<=\$\?\{)[\w]+(?=\})");
                        if (match.Success)
                        {
                            var valueResult = field.GetType().GetProperty(match.Value).GetValue(field);
                            if (valueResult != null && valueResult.ToString() != "")
                            {
                                newLine = newLine.Replace($"$?{{{match.Value}}}", "");
                                newFieldTamplate.AppendLine(newLine);
                            }
                        }
                    }
                    else
                    {
                        newFieldTamplate.AppendLine(newLine);
                    }
                }
                var fieldContent = newFieldTamplate.ToString();
                foreach (var pro in field.GetType().GetProperties())
                {
                    if (pro.Name != "DBType")
                    {
                        fieldContent = fieldContent.Replace($"${{{pro.Name}}}", pro.GetValue(field)?.ToString());
                    }
                    else
                    {
                        fieldContent = fieldContent.Replace("${DBType}", _typeMap[field.DBType]);
                    }
                }
                fields.AppendLine(fieldContent);
            }
            return fields.ToString();
        }
        /// <summary>
        /// 处理输出路径
        /// </summary>
        /// <param name="options"></param>
        /// <param name="dataBaseName"></param>
        /// <returns></returns>
        private string GetOut(CommandOptions options, string dataBaseName)
        {
            if (options.ContainsKey("--out"))
            {
                return options["--out"];
            }
            else
            {
                var basePath = $"{Directory.GetCurrentDirectory()}/{dataBaseName}";
                Directory.CreateDirectory(basePath);
                return basePath;
            }
        }
        /// <summary>
        /// 处理模板
        /// </summary>
        /// <param name="options"></param>
        /// <returns></returns>
        private (string Template, string Extension) GetTamplate(CommandOptions options)
        {
            var template = @"
using System;

namespace ${DataBaseName}
{
    /// <summary>
    /// ${TableDescribe}
    /// </summary>
    public class ${TableName}
    {
        ${Fields}
        $?{FieldDescribe}/// <summary>
        $?{FieldDescribe}/// ${FieldDescribe}
        $?{FieldDescribe}/// </summary>
        $?{FieldSize}[BField(Length=${FieldSize},Name=""${FieldName}"")]
        public ${DBType} ${FieldName}
        { get; set; }
        ${Fields}
    }
}
";
            if (options.ContainsKey("--tep"))
            {
                var path = options["--tep"].ToLower();
                if (path == "cs")
                {
                    return (template, ".cs");
                }

                if (path.StartsWith("http"))
                {
                    var client = new HttpClient();
                    var request = new HttpRequestMessage(HttpMethod.Get, path);
                    var response = client.SendAsync(request).Result;
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        return (response.Content.ReadAsStringAsync().Result, Path.GetExtension(path));
                    }
                    else
                    {
                        throw new ApplicationException("获取模版失败");
                    }
                }
                else
                {
                    return (File.ReadAllText(path, Encoding.UTF8), Path.GetExtension(path));
                }
            }
            else
            {
                return (template, ".cs");
            }
        }
        Dictionary<string, string> _typeMap;
        public CSharpBuilder(string dbType)
        {
            switch (dbType)
            {
                case "postgresql":
                    _typeMap = new Dictionary<string, string>
                    {
                        {"bigint","long" },//   int8    有符号8字节整数
                        {"bigserial" ,"long" },//   serial8 自增8字节整数
                        {"bit","bool" },//  [ (n) ]     定长位串            
                        {"boolean","bool" }, //bool    逻辑布尔值(真/假)
                        {"bool","bool" }, //bool    逻辑布尔值(真/假)
                        {"character","string" }, //varying [ (n) ]   varchar [ (n) ] 可变长字符串              
                        {"cidr","string" }, //      IPv4 或 IPv6 网络地址
                        {"date","DateTime" }  ,//        日历日期(年, 月, 日)
                        {"double","double" }  ,//  precision    float8  双精度浮点数(8字节)
                        {"inet","string" }  ,//       IPv4 或 IPv6 主机地址
                        {"int4","int" } , // int, int4   有符号 4 字节整数  
                        {"integer","int" } , // int, int4   有符号 4 字节整数  
                        {"macaddr","string" } , //    MAC (Media Access Control)地址
                        {"money","decimal" },  //      货币金额
                        {"numeric","decimal" } , //  [ (p, s) ]  decimal [ (p, s) ]  可选精度的准确数值数据类型
                        {"real","float" },  //    float4  单精度浮点数(4 字节)
                        {"smallint","short" },  //    int2    有符号 2 字节整数
                        {"smallserial","short" },  // serial2 自增 2 字节整数
                        {"serial","int" },  //   serial4 自增 4 字节整数
                        {"text","string" },  //        可变长字符串
                        {"varchar","string" },  //        可变长字符串
                        {"time","DateTime" } , //  [ (p) ] [ without time zone ]      一天中的时刻(无时区)
                       // {"time","DateTime" } , // [ (p) ] with time zone timetz  一天中的时刻,含时区
                        {"timestamp","DateTime" },  //  [ (p) ] [ without time zone ]     日期与时刻(无时区)
                       // {"timestamp","DateTime" },  // [ (p) ] with time zone    timestamptz 日期与时刻,含时区
                        {"tsquery","string" },  //    文本检索查询
                        {"tsvector","string" },  //      文本检索文档
                        {"txid_snapshot","string" },  //      用户级别的事务ID快照
                        {"uuid","string" } , //     通用唯一标识符
                        {"xml","string" } , //  XML 数据
                        {"json","string" },  //      JSON 数据
                    };
                    break;
                case "mysql":
                    _typeMap = new Dictionary<string, string>
                    {
                        {"char","char" },
                        {"varchar","string" },
                        {"tinytext","string" },
                        {"text","string" },
                        {"blob","string" },
                        {"mediumtext","string" },
                        {"mediumblob","string" },
                        {"longblob","string" },
                        {"longtext","string" },
                        {"tinyint","short" },
                        {"smallint","short" },
                        {"mediumint","short" },
                        {"int","int" },
                        {"bigint","long" },
                        {"float","float" },
                        {"double","double" },
                        {"decimal","decimal" },
                        {"date","DateTime" },
                        {"datetime","DateTime" },
                        {"timestamp","string" },
                        {"time","DateTime" },
                        {"boolean","bool" },
                    };
                    break;
                case "mssql":
                    _typeMap = new Dictionary<string, string>
                    {
                        { "char", "string" },// 固定长度的字符串。最多 8,000 个字符。  n
                        { "varchar", "string" },//(n)  可变长度的字符串。最多 8,000 个字符。   
                        { "text", "string" },//    可变长度的字符串。最多 2GB 字符数据。
                        { "nchar", "string" },//(n)    固定长度的 Unicode 数据。最多 4,000 个字符。   
                        { "nvarchar", "string" },//(n) 可变长度的 Unicode 数据。最多 4,000 个字符。   
                        { "ntext", "string" },//   可变长度的 Unicode 数据。最多 2GB 字符数据。
                        { "bit", "bool" },//  允许 0、1 或 NULL
                        { "binary", "string" },//(n)   固定长度的二进制数据。最多 8,000 字节。   
                        { "varbinary", "string" },//(n)    可变长度的二进制数据。最多 8,000 字节。   
                        { "image", "string" },//   可变长度的二进制数据。最多 2GB。
                        { "tinyint", "byte" },// 允许从 0 到 255 的所有数字。  1 字节
                        { "smallint", "short" },//    允许从 -32,768 到 32,767 的所有数字。  2 字节
                        { "int", "int" },// 允许从 -2,147,483,648 到 2,147,483,647 的所有数字。  4 字节
                        { "bigint", "long" },//  允许介于 -9,223,372,036,854,775,808 和 9,223,372,036,854,775,807 之间的所有数字。  8 字节
                        { "decimal", "decimal" },//
                        { "numeric", "decimal" },//
                        { "smallmoney", "decimal" },//  介于 -214,748.3648 和 214,748.3647 之间的货币数据。  4 字节
                        { "money", "decimal" },//   介于 -922,337,203,685,477.5808 和 922,337,203,685,477.5807 之间的货币数据。  8 字节
                        { "float", "float" },//   从 -1.79E + 308 到 1.79E + 308 的浮动精度数字数据。 参数 n 指示该字段保存 4 字节还是 8 字节。f
                        { "real", "double" },//    从 -3.40E + 38 到 3.40
                        { "datetime", "DateTime" },//    从 1753 年 1 月 1 日 到 9999 年 12 月 31 日,精度为 3.33 毫秒。  8 bytes
                        { "datetime2", "DateTime" },//   从 1753 年 1 月 1 日 到 9999 年 12 月 31 日,精度为 100 纳秒。  6-8 bytes
                        { "smalldatetime", "DateTime" },//   从 1900 年 1 月 1 日 到 2079 年 6 月 6 日,精度为 1 分钟。  4 bytes
                        { "date", "DateTime" },//    仅存储日期。从 0001 年 1 月 1 日 到 9999 年 12 月 31 日。  3 bytes
                        { "time", "DateTime" },//    仅存储时间。精度为 100 纳秒。  3-5 bytes
                        { "datetimeoffset", "string" },//  与 datetime2 相同,外加时区偏移。  8-10 bytes
                        { "timestamp", "string" }//   存储唯一的
                    };
                    break;
            }
        }
    }
}

  CSharpBuilder类完成的是根据gen命令的参数,生成模板,类型映射来生成一个纯文本的.cs文件。

  感觉实现的不太优美,有时间再优化,如果大家在模板这块有什么好的建议,请留言。

  其实还有一个处理的地方,就是各种命令进来以后,分各归其主方法去执行,实现如下

 static class CSRobotTools
    {
        static Dictionary<string, Func<CommandOptions, bool>> _CSRobotDic;
        static CSRobotTools()
        {
            _CSRobotDic = new Dictionary<string, Func<CommandOptions, bool>> {
            {"-info", Info},
            {"-h",Help},
            {"gen",GenerateEntityTool.GenerateEntity}
        };
        }
        public static bool Run(string[] args)
        {
            var options = GetOptions(args);
            if (args.Length == 0)
            {
                return _CSRobotDic["--info"](options);
            }
            else if (_CSRobotDic.ContainsKey(args[0]))
            {
                return _CSRobotDic[args[0]](options);
            }
            else
            {
                return false;
            }
        }
        static bool Help(CommandOptions options)
        {
            var mgr = new ResourceManager("CSRobot.Resource.gen", Assembly.GetExecutingAssembly());
            Console.WriteLine(mgr.GetString("csrobot-h"), Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion.ToString());
            return true;
        }
        static bool Info(CommandOptions options)
        {
            var mgr = new ResourceManager("CSRobot.Resource.gen", Assembly.GetExecutingAssembly());
            Console.WriteLine(mgr.GetString("csrobot-info"), Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion.ToString());
            return true;
        }
        static CommandOptions GetOptions(string[] args)
        {
            var options = new CommandOptions();
            for (var i = 1; i < args.Length; i++)
            {
                if (string.IsNullOrEmpty(args[i].Trim()))
                {
                    continue;
                }
                var arr = args[i].Split("=");
                if (arr.Length < 2)
                {
                    options.Add(arr[0], null);
                }
                else
                {
                    options.Add(arr[0], arr[1]);
                }
            }
            return options;
        }
    }

  接下来要做的:

  1、进一步细化各部门实现

  2、数据库到csharp的类型map放的更灵活

  3、模板适配更丰富,更灵活

 

  想要更快更方便的了解相关知识,可以关注微信公众号 
 

 

 

posted @ 2022-02-04 08:30  桂素伟  阅读(186)  评论(0编辑  收藏  举报