Visual Studio Package 插件開發之自動生成實體工具(Visual Studio SDK)

前言

  這一篇是VS插件基於Visual Studio SDK擴展開發的,可能有些朋友看到【生成實體】內心可能會暗想,T4模板均可以作了、動軟不是已經作了麼、不就是讀庫保存文件到指定路徑麼……html

  我但願作的效果是:git

  1.工具集成到vs上github

  2.動做完成後體現到項目(添加、刪除項目項)sql

  3.使用簡單、輕量、靈活(配置化)數據庫

  4.不依賴ORM(前兩點有點像EF的DBFirst吧?)架構

  文章最後會給上源碼地址。ssh

   下面是效果圖:編輯器

處理流程

  

  以上是完整處理流程,我打算選擇部分流程來說。若是有對Visual Studio Package開發還沒一個認識,能夠看我以前寫的一篇Visual Studio Package 插件開發ide

按鈕的位置

  

  從上圖看見,按鈕是在選中項目右鍵彈出的菜單欄裏。工具

  打開vsct文件,修改Group的Parent節點,修改對應的guid和id。

  以前那邊文章有提到在文件:您的vs安裝目錄\VisualStudio2013\VSSDK\VisualStudioIntegration\Common\Inc\vsshlids.h 能夠找到須要修改的名稱,可是右鍵是沒有在文件裏定義,所以咱們須要另外換一種方法。

  一、打開註冊表編輯器(打開運行窗口,輸入regedit),

  二、路徑[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\12.0\General],

  三、右擊-新建-DWORD(32-位)值(D),其命名爲EnableVSIPLogging

  四、並將其值改成1。重啓VS,打開項目

  五、按下Ctrl+Shift,對項目點擊右鍵,就會彈出窗口(以下圖)

  

  Guid和CmdID的值就是咱們須要的,在vsct文件Symbols節點添加GuidSymbol項,value上圖的{D309F791-903F-11D0-9EFC-00A0C911004F},IDSymbolvalue1026。

  最後在Group的Parent節點的屬性guidid改成與上面對應下面代碼爲例子。

<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.0\Common7\IDE\devenv.exe

  2.命令行參數:/rootsuffix Exp

  

  估計有同窗會製做本身的圖標,另外附上兩條icon製做的網站:

  http://iconfont.cn/search/index

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

結尾

  整篇文章的技術難點並很少,可是由於插件開發的資料相對較少,80%的時間花去找接口文檔、找資料。

  此工具的原型是公司架構師的,公司全部開發都在用,可是他把源碼丟了………………好奇心使我從新實現了一份,固然了,說不定哪天帶團隊的時候會用上。

  最後雙手奉上源碼,並非什麼牛逼的東西,但願能夠幫助須要的同窗。https://github.com/SkyChenSky/AutoBuildEntity

  若是本篇文章對您有幫助,能夠點擊左下角的推薦,這是給我最大的鼓勵,若是有什麼建議和優化,能夠在下面評論提出,謝謝。

相關文章
相關標籤/搜索