zoukankan      html  css  js  c++  java
  • 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

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

  • 相关阅读:
    解决 搭建Jekins过程中 启动Tomcat的java.net.UnknownHostException异常
    射手和农场主
    java 和 JS(javaScript)中的反斜杠正则转义
    分享修改密码的SharePoint Web part: ITaCS Change Password web part
    分享微软官方Demo用的SharePoint 2010, Exchange 2010, Lync 2010虚拟机
    Office 365 的公共网站的一些限制及解决的办法
    SharePoint 2013 关闭 customErrors
    安装 KB2844286 导致SharePoint 2010 XSLT web part 显示出现错误
    安装Office Web Apps Server 2013 – KB2592525安装失败
    如何将hyper-v虚拟机转换成vmware的虚拟机- 转换SharePoint 2010 Information Worker Demonstration and Evaluation Virtual Machine (SP1)
  • 原文地址:https://www.cnblogs.com/skychen1218/p/6848128.html
Copyright © 2011-2022 走看看