Visual Studio Package 插件开发之自动生成实体工具
前言
这一篇是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安装目录\VisualStudio2013\VSSDK\VisualStudioIntegration\Common\Inc\vsshlids.h 可以找到需要修改的名称,但是右键是没有在文件里定义,因此我们需要另外换一种方法。
1、打开注册表编辑器(打开运行窗口,输入regedit),
2、路径[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\12.0\General],
3、右击-新建-DWORD(32-位)值(D),其命名为EnableVSIPLogging
4、并将其值改为1。重启VS,打开项目
5、按下Ctrl+Shift,对项目点击右键,就会弹出窗口(如下图)
Guid和CmdID的值就是我们需要的,在vsct文件Symbols节点添加GuidSymbol项,value上图的{D309F791-903F-11D0-9EFC-00A0C911004F},IDSymbol项value为1026。
最后在Group的Parent节点的属性guid和id改为与上面对应,下面代码为例子。
PS:上面方法有点久远了,现在2017、2019可以用新的方式来查找需要的功能guid与cmdID。
非常感谢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>
读取选中项目信息
重点是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 == )
{
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;
}
读取实体配置信息
配置存放两点信息:数据库连接、类文件模版,同时我们约定存放在项目根目录下。如下图
那么,剩下就是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;
}
}
读取物理表
查询当前数据库的表集合,传给窗体做列表展示,直接上代码:
/// <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[].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();
} }
读取表结构
选择响应的表后,查询出对应的表结构,一般实体的所需要的信息有:列名、列备注、类型、长度、是否主键、是否自增长、是否可空,继续上代码:
/// <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();
}
}
根据模板生成代码
开始我是尝试用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();
}
之前已经拿到的文件模版,通过上面的方法输出类文本,保存到选中项目的根目录下。
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, , byteFile.Length);
}
return path;
}
}
操作项目
终于到了最后一步了,部分人以为保存了文件后就完事了,最后通过包含文件就完事了。我们还是有点追求的,既然做成了插件就要更加的方便化。
通过之前[读取选中项目信息]步骤拿到的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();
}
附加
部分同学可能想调试的时候会出现:无法直接启动“类库输出类型”项目,可以在项目属性-调试配置:
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
如果本篇文章对您有帮助,可以点击左下角的推荐,这是给我最大的鼓励,如果有什么建议和优化,可以在下面评论提出,谢谢。
Visual Studio Package 插件开发之自动生成实体工具的更多相关文章
- Visual Studio Package 插件开发之自动生成实体工具(Visual Studio SDK)
前言 这一篇是VS插件基于Visual Studio SDK扩展开发的,可能有些朋友看到[生成实体]心里可能会暗想,T4模板都可以做了.动软不是已经做了么.不就是读库保存文件到指定路径么…… 我希望做 ...
- 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生
[转].NET(C#):浅谈程序集清单资源和RESX资源 目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...
- Visual Studio Package 插件开发
背景 这段时间公司新做了一个支付系统,里面有N个后台服务,每次有更新修改,拷贝打包发布包"不亦乐乎"...于是我想要不要自己定制个打包插件. 部分朋友可能会认为,有现成的可以去找一 ...
- Visual Studio Package 插件开发(Visual Studio SDK)
背景 这段时间公司新做了一个支付系统,里面有N个后台服务,每次有更新修改,拷贝打包发布包“不亦乐乎”...于是我想要不要自己定制个打包插件. 部分朋友可能会认为,有现成的可以去找一个,干嘛不用持续集成 ...
- 让Visual Studio 2013为你自动生成XML反序列化的类
Visual Sutdio 2013增加了许多新功能,其中很多都直接提高了对代码编辑的便利性.如: 1. 在代码编辑界面的右侧滚动条上显示不同颜色的标签,让开发人员可以对所编辑文档的修改.查找.定位情 ...
- Visual Studio 2017 怎么将自动生成属性设置为旧版格式
工具:Visual Studio 2017 1.点击工具,进入选项 2.选项窗口左侧找到C#--代码样式,点击 3.找到表达式首选项中:使用属性的表达式主体.使用索引器的表达式主体和使用访问器的表达式 ...
- 使用T4为数据库自动生成实体类
T4 (Text Template Transformation Toolkit) 是一个基于模板的代码生成器.使用T4你可以通过写一些ASP.NET-like模板,来生成C#, T-SQL, XML ...
- visual studio单项目一次生成多框架类库、多框架项目合并
目录 不同平台框架项目使用同一套代码,一次编译生成多个框架类库 需要先了解的东西 分析 添加PropertyGroup 多目标平台 编译符号和输出目录设置 添加依赖 代码文件处理 主副平台项目文件处理 ...
- Mybatis自动生成实体类
Maven自动生成实体类需要的jar包 一.pom.xml中 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns ...
随机推荐
- javaWEB与cookie
Cookie1. Http协议与Cookie(了解) * Cookie是HTTP协议制定的!先由服务器保存Cookie到浏览器,再下次浏览器请求服务器时把上一次请求得到Cookie再归还给服务器 ...
- 什么是测试开发工程师-google的解释
什么是测试开发工程师-google的解释 “ 软件测试开发工程师[SET or Software Engineer in Test],和软件开发工程师一样是开发工程师,主要负责软件的可测试性.他们参与 ...
- Appium和Robotium在文字输入上的区别
Appium和Robotium在文字输入上的区别 Appium和Robotium在对文本框进行输入时有一定的区别: Appium在输入文字时需要调用系统键盘 Robotium在输入文字是根本不需要 ...
- Android kernel LOGO的更换方法
[从制作logo到LCD显示或者VGA显示logo] 1.制作logo的方法: 首先选择一个自己喜欢的图片,然后通过GIMP软件将该图片保存为.png格式, 变换方式这个就不说了(very easy) ...
- 使用HTML5的canvas做图片剪裁
前言 图片裁剪上传,不仅是一个很贴合用户体验的功能,还能够统一特定图片尺寸,优化网站排版,一箭双雕. 需求就是那么简单,在浏览器里裁剪图片并上传到服务器. 我第一个想到的方法就是,将图片和裁剪参数(x ...
- 设计模式的征途—3.工厂方法(Factory Method)模式
上一篇的简单工厂模式虽然简单,但是存在一个很严重的问题:当系统中需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违背开闭原则.如何实现新增新产品而 ...
- 浩哥解析MyBatis源码(十)——Type类型模块之类型处理器
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6715063.html 1.回顾 之前的两篇分别解析了类型别名注册器和类型处理器注册器,此二 ...
- 初学 Java Script (数据类型)
简介:JavaScript是一种属于网络的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果.Javascript脚本语言同其他语言一样,有它自 ...
- 简单聊聊Storm的流分组策略
简单聊聊Storm的流分组策略 首先我要强调的是,Storm的分组策略对结果有着直接的影响,不同的分组的结果一定是不一样的.其次,不同的分组策略对资源的利用也是有着非常大的不同,本文主要讲一讲loca ...
- JS判断值是否是正数
1.使用isNaN()函数 isNaN()的缺点就在于 null.空格以及空串会被按照0来处理 NaN: Not a Number /** *判断是否是数字 * **/ function isReal ...