基于 Roslyn 实现动态编译
基于 roslyn 实现动态编译
intro
之前做的一个数据库小工具可以支持根据 model 代码文件生成创建表的 sql 语句,原来是基于 codedom 实现的,最近改成使用基于 roslyn 去做了。实现的原理在于编译选择的model 文件生成一个程序集,再从这个程序集中拿到 model (数据库表)信息以及属性信息(数据库表字段信息),拿到数据库表以及表字段信息之后就根据数据库类型生成大致的创建表的 sql 语句。
codefirst 效果如下图所示:
如果你还不知道这个数据库小工具,欢迎访问这个项目了解更多https://github.com/weihanli/dbtool
迁移原因
最初的 codedom 也是可以用的,但是有一些比较新的 c# 语法不支持,比如 c#6 中的指定属性初始值 public int number {get;set;} = 1;
,最初我是迁移到了 microsoft.codedom.providers.dotnetcompilerplatform
这个是一个 codedom 过渡到 roslyn 的实现,他提供了和 codedom 差不多的语法,支持 c#6 的语法。但是还是有个问题,我的项目使用了新的项目文件格式,在 vs 中可以编译通过,但是 dotnet cli 编译不通过,详见 issue https://github.com/aspnet/roslyncodedomprovider/issues/51
这个问题已经过去一年了仍未解决,最终决定迁移到 roslyn,直接使用 roslyn 实现动态编译。
对 codedom 感兴趣的童鞋可以看 dbtool 之前的 commit 记录,在此不多叙述。
使用 roslyn 实现动态编译
roslyn 好像没有直接根据几个文件去编译(可能有只是我没发现),我就使用了一个比较笨的办法,把几个文件的内容都读出来,合并在一起(命名空间需要去重),然后去编译,完整源代码地址
,实现代码如下:
/// <summary> /// 从 源代码 中获取表信息 /// </summary> /// <param name="sourcefilepaths">sourcecodefiles</param> /// <returns></returns> public static list<tableentity> getableentityfromsourcecode(params string[] sourcefilepaths) { if (sourcefilepaths == null || sourcefilepaths.length <= 0) { return null; } var usinglist = new list<string>(); var sourcecodetextbuilder = new stringbuilder(); foreach (var path in sourcefilepaths) { foreach (var line in file.readalllines(path)) { if (line.startswith("using ") && line.endswith(";")) { // usinglist.addifnotcontains(line); } else { sourcecodetextbuilder.appendline(line); } } } var sourcecodetext = $"{usinglist.stringjoin(environment.newline)}{environment.newline}{sourcecodetextbuilder}"; // 获取完整的代码 var systemreference = metadatareference.createfromfile(typeof(object).assembly.location); var annotationreference = metadatareference.createfromfile(typeof(tableattribute).assembly.location); var weihanlicommonreference = metadatareference.createfromfile(typeof(idependencyresolver).assembly.location); var syntaxtree = csharpsyntaxtree.parsetext(sourcecodetext, new csharpparseoptions(languageversion.latest)); // 获取代码分析得到的语法树 var assemblyname = $"dbtool.dynamicgenerated.{objectidgenerator.instance.newid()}"; // 创建编译任务 var compilation = csharpcompilation.create(assemblyname) //指定程序集名称 .withoptions(new csharpcompilationoptions(outputkind.dynamicallylinkedlibrary))//输出为 dll 程序集 .addreferences(systemreference, annotationreference, weihanlicommonreference) //添加程序集引用 .addsyntaxtrees(syntaxtree) // 添加上面代码分析得到的语法树 ; var assemblypath = applicationhelper.mappath($"{assemblyname}.dll"); var compilationresult = compilation.emit(assemblypath); // 执行编译任务,并输出编译后的程序集 if (compilationresult.success) { // 编译成功,获取编译后的程序集并从中获取数据库表信息以及字段信息 try { byte[] assemblybytes; using (var fs = file.openread(assemblypath)) { assemblybytes = fs.tobytearray(); } return getableentityfromassembly(assembly.load(assemblybytes)); } finally { file.delete(assemblypath); // 清理资源 } } var error = new stringbuilder(compilationresult.diagnostics.length * 1024); foreach (var t in compilationresult.diagnostics) { error.appendline($"{t.getmessage()}"); } // 获取编译错误 throw new argumentexception($"所选文件编译有错误{environment.newline}{error}"); }
reference
上一篇: c#两种方式调用google地球,调用COM API以及调用GEPLUGIN 与js交互,加载kml文件,dae文件。将二维高德地图覆盖到到三维谷歌地球表面。
下一篇: C# -- 使用ODBC连接数据库
推荐阅读
-
基于Vue+elementUI实现动态表单的校验功能(根据条件动态切换校验格式)
-
基于Bootstrap和JQuery实现动态打开和关闭tab页的实例代码
-
基于 Roslyn 实现动态编译
-
C#基于QRCode实现动态生成自定义二维码图片功能示例
-
AngularJS实现动态编译添加到dom中的方法
-
JavaScript基于Ajax实现不刷新在网页上动态显示文件内容
-
基于Nacos实现Spring Cloud Gateway实现动态路由的方法
-
基于 Roslyn 实现动态编译
-
分享基于.NET动态编译&Newtonsoft.Json封装实现JSON转换器(JsonConverter)原理及JSON操作技巧
-
基于Vue+elementUI实现动态表单的校验功能(根据条件动态切换校验格式)