• Visual Studio Package 插件开发之自动生成实体工具(Visual Studio SDK)


    前言

      这一篇是VS插件基于Visual Studio SDK扩展开发的,可能有些朋友看到【生成实体】心里可能会暗想,T4模板都可以做了、动软不是已经做了么、不就是读库保存文件到指定路径么……

      我希望做的效果是:

      1.工具集成到vs上

      2.动作完成后体现到项目(添加、删除项目项)

      3.使用简单、轻量、灵活(配置化)

      4.不依赖ORM(前两点有点像EF的DBFirst吧?)

      文章最后会给上源码地址。

       下面是效果图:

    处理流程

      

      以上是完整处理流程,我打算选择部分流程来讲。如果有对Visual Studio Package开发还没一个认识,可以看我之前写的一篇Visual Studio Package 插件开发

    按钮的位置

      

      从上图看见,按钮是在选中项目右键弹出的菜单栏里。

      打开vsct文件,修改Group的Parent节点,修改对应的guid和id。

      之前那边文章有提到在文件:您的vs安装目录VisualStudio2013VSSDKVisualStudioIntegrationCommonIncvsshlids.h 可以找到需要修改的名称,但是右键是没有在文件里定义,因此我们需要另外换一种方法。

      1、打开注册表编辑器(打开运行窗口,输入regedit),

      2、路径[HKEY_CURRENT_USERSoftwareMicrosoftVisualStudio12.0General],

      3、右击-新建-DWORD(32-位)值(D),其命名为EnableVSIPLogging

      4、并将其值改为1。重启VS,打开项目

      5、按下Ctrl+Shift,对项目点击右键,就会弹出窗口(如下图)

      

      Guid和CmdID的值就是我们需要的,在vsct文件Symbols节点添加GuidSymbol项,value上图的{D309F791-903F-11D0-9EFC-00A0C911004F},IDSymbolvalue1026。

      最后在Group的Parent节点的属性guidid改为与上面对应下面代码为例子。

    PS:上面方法有点久远了,现在2017、2019可以用新的方式来查找需要的功能guid与cmdID。

    在VS的【扩展与更新】搜索并安装Extensibility Tools,然后在vs【视图】-【 Enable VSIP Logging】点击并重启后,就可以用ctrl+shirt+右键点击需要查的界面,就可以弹出需要的信息,我测试过vs2017可用。

    非常感谢yanusosu兄弟的贡献。

    <CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    
      <Commands package="guidAutoBuildEntityPkg">
    
        <Groups>
          <Group guid="guidAutoBuildEntityCmdSet" id="MyMenuGroup" priority="0x0600">
            <Parent guid="guidCodeWindowRightClickCmdSet" id="CodeWindowRightClickMenu"/>
          </Group>
        </Groups>
    
      </Commands>
    
      <Symbols>
        <GuidSymbol name="guidAutoBuildEntityPkg" value="{c095f8f8-3f87-4eac-8dc0-44939a85b2f2}" />
    
        <GuidSymbol name="guidCodeWindowRightClickCmdSet" value="{D309F791-903F-11D0-9EFC-00A0C911004F}">
          <IDSymbol name="CodeWindowRightClickMenu" value="1026" />
        </GuidSymbol>
    
      </Symbols>
    
    </CommandTable>
    View Code

    读取选中项目信息

       重点是DTE 接口的使用,MSDN的描述是:DTE 接口Visual Studio 自动化对象模型中的顶级对象。强大到当前开发环境中任何属性可以拿到例如:当前打开的文档集合,解决方案下的项目信息……剩下自己看,传送门

       下面是代码示例:

           var dte = (DTE)GetService(typeof(SDTE));
    
             /// <summary>
            /// 获取选中项目的信息
            /// </summary>
            /// <param name="dte"></param>
            /// <returns></returns>
            public static SelectedProject GetSelectedProjectInfo(this DTE dte)
            {
                var selectedItems = dte.SelectedItems;
    
                var projectName = (from SelectedItem item in selectedItems select item.Name).ToList();
    
                if (!selectedItems.MultiSelect && selectedItems.Count == 1)
                {
                    var selectProject = selectedItems.Item(projectName.First());
    
                    var projectFileList = (from ProjectItem projectItem in selectProject.Project.ProjectItems
                                           where projectItem.Name.EndsWith(".cs")
                                           select Path.GetFileNameWithoutExtension(projectItem.Name)).ToList();
    
                    return new SelectedProject(selectProject.Project.FullName, selectProject.Project, projectFileList);
                }
    
                return null;
            }        
    View Code

    读取实体配置信息

      配置存放两点信息:数据库连接、类文件模版,同时我们约定存放在项目根目录下。如下图

      

      那么,剩下就是XML的基本获取处理了。__entity.xml的模版在源码里,可自行拷贝去需要使用的项目,以下是代码示例

    private void AutoBuildEntityEvent(object sender, EventArgs e)
            {
                var autoBuildEntityContent = new AutoBuildEntityContent ();
    
                //读取选中项目下的配置信息
                var entityXmlModel = new EntityXml(autoBuildEntityContent.SelectedProject.EntityXmlPath);
                entityXmlModel.Load();
                autoBuildEntityContent.EntityXml = entityXmlModel;
    
                new MainForm(autoBuildEntityContent).ShowDialog();
            }
    
        public class EntityXml
        {
            private readonly string _path;
    
            public EntityXml(string path)
            {
                _path = path;
            }
    
            public string ConnString { get; private set; }
    
            public string EntityTemplate { get; private set; }
    
            /// <summary>
            /// 读取_entity.xml
            /// </summary>
            /// <returns></returns>
            public EntityXml Load()
            {
                var xml = new XmlDocument();
                xml.Load(_path);
    
                var autoEntityNode = xml.SelectSingleNode("AutoEntity");
                if (autoEntityNode != null)
                {
                    var connStringNode = autoEntityNode.SelectSingleNode("ConnString");
                    if (connStringNode != null)
                    {
                        ConnString = connStringNode.InnerText;
                    }
                    var templatesNodes = autoEntityNode.SelectSingleNode("Template");
                    if (templatesNodes != null)
                    {
                        EntityTemplate = templatesNodes.InnerText;
                    }
                }
    
                return this;
            }
        }
    View Code

    读取物理表

      查询当前数据库的表集合,传给窗体做列表展示,直接上代码:

    /// <summary>
        /// 物理表
        /// </summary>
        public class DbTable
        {
            public string TableName { get; private set; }
    
            public List<TableColumn> Columns { get; set; }
    
            private readonly string _conn;
    
            public DbTable(string conn)
            {
                _conn = conn;
            }
    
            public DbTable(string tableName, List<TableColumn> columns)
            {
                TableName = tableName;
                Columns = columns;
            }
    
            public List<string> QueryTablesName()
            {
                var result = SqlHelper.Query(_conn, @"SELECT  name FROM    sysobjects WHERE  xtype IN ( 'u','v' ); ");
    
                return (from DataRow row in result.Rows select row[0].ToString()).ToList();
            }
    
            public List<DbTable> GetTables(List<string> tablesName)
            {
                if (!tablesName.Any())
                    return new List<DbTable>();
    
                var t = new TableColumn(_conn);
    
                var columns = t.QueryColumn(tablesName);
    
                return columns.GroupBy(a => a.TableName).Select(a => new DbTable(a.Key, a.ToList())).ToList();
            }
    
        }
    View Code

    读取表结构

      选择响应的表后,查询出对应的表结构,一般实体的所需要的信息有:列名、列备注、类型、长度、是否主键、是否自增长、是否可空,继续上代码:

    /// <summary>
        /// 物理表的列信息
        /// </summary>
        public class TableColumn
        {
            private readonly string _connStr;
            public TableColumn()
            {
    
            }
            public TableColumn(string connStr)
            {
                _connStr = connStr;
            }
    
            public string TableName { get; private set; }
    
            public string Name { get; private set; }
    
            public string Remark { get; private set; }
    
            public string Type { get; private set; }
    
            public int Length { get; private set; }
    
            public bool IsIdentity { get; private set; }
    
            public bool IsKey { get; private set; }
    
            public bool IsNullable { get; private set; }
    
            public string CSharpType
            {
                get
                {
                    return SqlHelper.MapCsharpType(Type, IsNullable);
                }
            }
    
            /// <summary>
            /// 查询列信息
            /// </summary>
            /// <param name="tablesName"></param>
            /// <returns></returns>
            public List<TableColumn> QueryColumn(List<string> tablesName)
            {
                #region 表结构
    
                var paramKey = string.Join(",", tablesName.Select((a, index) => "@p" + index));
                var paramVal = tablesName.Select((a, index) => new SqlParameter("@p" + index, a)).ToArray();
                var sql = string.Format(@"SELECT  obj.name AS tablename ,
            col.name ,
            ISNULL(ep.[value], '') remark ,
            t.name AS type ,
            col.length ,
            COLUMNPROPERTY(col.id, col.name, 'IsIdentity') AS isidentity ,
            CASE WHEN EXISTS ( SELECT   1
                               FROM     dbo.sysindexes si
                                        INNER JOIN dbo.sysindexkeys sik ON si.id = sik.id
                                                                  AND si.indid = sik.indid
                                        INNER JOIN dbo.syscolumns sc ON sc.id = sik.id
                                                                  AND sc.colid = sik.colid
                                        INNER JOIN dbo.sysobjects so ON so.name = si.name
                                                                  AND so.xtype = 'PK'
                               WHERE    sc.id = col.id
                                        AND sc.colid = col.colid ) THEN 1
                 ELSE 0
            END AS iskey ,
            col.isnullable
    FROM    dbo.syscolumns col
            LEFT  JOIN dbo.systypes t ON col.xtype = t.xusertype
            INNER JOIN dbo.sysobjects obj ON col.id = obj.id
                                             AND obj.xtype IN ( 'U', 'v' )
                                             AND obj.status >= 0
            LEFT  JOIN dbo.syscomments comm ON col.cdefault = comm.id
            LEFT  JOIN sys.extended_properties ep ON col.id = ep.major_id
                                                     AND col.colid = ep.minor_id
                                                     AND ep.name = 'MS_Description'
            LEFT  JOIN sys.extended_properties epTwo ON obj.id = epTwo.major_id
                                                        AND epTwo.minor_id = 0
                                                        AND epTwo.name = 'MS_Description'
    WHERE   obj.name IN ({0});", paramKey);
    
                #endregion
    
                var result = SqlHelper.Query(_connStr, sql, paramVal);
    
                return (from DataRow row in result.Rows
                        select new TableColumn
                        {
                            IsIdentity = Convert.ToBoolean(row["isidentity"]),
                            IsKey = Convert.ToBoolean(row["iskey"]),
                            IsNullable = Convert.ToBoolean(row["isnullable"]),
                            Length = Convert.ToInt32(row["length"]),
                            Name = row["name"].ToString(),
                            Remark = row["remark"].ToString(),
                            TableName = row["tablename"].ToString(),
                            Type = row["type"].ToString()
                        }).ToList();
            }
        }
    View Code

    根据模板生成代码

      开始我是尝试用T4的,发现不方便,繁杂的声明。因此我选择了nVelocity,这里不做太多介绍,附上相关文章学习,传送门

    // <summary>
            /// 初始化模板引擎
            /// </summary>
            public static string ProcessTemplate(string template, Dictionary<string, object> param)
            {
                var templateEngine = new VelocityEngine();
                templateEngine.SetProperty(RuntimeConstants.RESOURCE_LOADER, "file");
    
                templateEngine.SetProperty(RuntimeConstants.INPUT_ENCODING, "utf-8");
                templateEngine.SetProperty(RuntimeConstants.OUTPUT_ENCODING, "utf-8");
    
                templateEngine.SetProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, AppDomain.CurrentDomain.BaseDirectory);
    
    
                var context = new VelocityContext();
                foreach (var item in param)
                {
                    context.Put(item.Key, item.Value);
                }
    
                templateEngine.Init();
    
    
                var writer = new StringWriter();
                templateEngine.Evaluate(context, writer, "mystring", template);
    
                return writer.GetStringBuilder().ToString();
            }
    View Code

      之前已经拿到的文件模版,通过上面的方法输出类文本,保存到选中项目的根目录下。

     public static class FilesHelper
        {
            public static string Write(string directory, string fileName, string content)
            {
                var path = Path.Combine(directory, fileName + ".cs");
                using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
                {
                    byte[] byteFile = Encoding.UTF8.GetBytes(content);
                    fs.Write(byteFile, 0, byteFile.Length);
                }
                return path;
            }
        }
    View Code

    操作项目

      终于到了最后一步了,部分人以为保存了文件后就完事了,最后通过包含文件就完事了。我们还是有点追求的,既然做成了插件就要更加的方便化。

      通过之前[读取选中项目信息]步骤拿到的EnvDTE.Project ProjectDte,使用以下扩展方法进行添加、删除项目项

     /// <summary>
            /// 添加项目项
            /// </summary>
            /// <param name="projectDte"></param>
            /// <param name="files"></param>
            public static void AddFilesToProject(this Project projectDte, List<string> files)
            {
                foreach (string file in files)
                {
                    projectDte.ProjectItems.AddFromFile(file);
                }
    
                if (files.Any())
                    projectDte.Save();
            }
    
            /// <summary>
            /// 排除项目项
            /// </summary>
            /// <param name="projectDte"></param>
            /// <param name="files"></param>
            public static void RemoveFilesFromProject(this Project projectDte, List<string> files)
            {
                foreach (string file in files)
                {
                    projectDte.ProjectItems.Item(Path.GetFileName(file)).Remove();
                }
    
                if (files.Any())
                    projectDte.Save();
            }
    View Code

    附加

      部分同学可能想调试的时候会出现:无法直接启动“类库输出类型”项目,可以在项目属性-调试配置:

      1.启动配置外部程序:C:Program Files (x86)Microsoft Visual Studio 12.0Common7IDEdevenv.exe

      2.命令行参数:/rootsuffix Exp

      

      估计有同学会制作自己的图标,另外附上两条icon制作的网站:

      http://iconfont.cn/search/index

      http://www.easyicon.net/covert/

    结尾

      整篇文章的技术难点并不多,但是因为插件开发的资料相对较少,80%的时间花去找接口文档、找资料。

      此工具的原型是公司架构师的,公司所有开发都在用,但是他把源码丢了………………好奇心使我重新实现了一份,当然了,说不定哪天带团队的时候会用上。

      最后双手奉上源码,并不是什么牛逼的东西,希望可以帮助需要的同学。https://github.com/SkyChenSky/AutoBuildEntity

      如果本篇文章对您有帮助,可以点击左下角的推荐,这是给我最大的鼓励,如果有什么建议和优化,可以在下面评论提出,谢谢。

  • 相关阅读:
    java实现文件变化监控
    java实现串口通讯
    Linux From Scratch(从零开始构建Linux系统,简称LFS)(三)
    Linux From Scratch(从零开始构建Linux系统,简称LFS)(二)
    Linux From Scratch(从零开始构建Linux系统,简称LFS)(一)
    OAuth 2.0
    DDD学习专题
    Java未来也许不再是电商的首选开发语言
    Axon
    CQRS模式介绍
  • 原文地址:https://www.cnblogs.com/skychen1218/p/6848128.html
Copyright © 2020-2023  润新知