• 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、模板适配更丰富,更灵活

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

     

  • 相关阅读:
    velocity模板引擎学习(2)-velocity tools 2.0
    silverlight: http请求的GET及POST示例
    职责链(Chain of Responsibility)模式在航空货运中的运用实例
    H2 Database入门
    velocity模板引擎学习(1)
    Struts2、Spring MVC4 框架下的ajax统一异常处理
    企业应用通用架构图
    nginx学习(2):启动gzip、虚拟主机、请求转发、负载均衡
    nginx学习(1):编译、安装、启动
    eclipse/intellij Idea集成jetty
  • 原文地址:https://www.cnblogs.com/axzxs2001/p/15862445.html
Copyright © 2020-2023  润新知