基于 Roslyn 实现动态编译
基于 Roslyn 實(shí)現(xiàn)動(dòng)態(tài)編譯
Intro
之前做的一個(gè)數(shù)據(jù)庫小工具可以支持根據(jù) Model 代碼文件生成創(chuàng)建表的 sql 語句,原來是基于 CodeDom 實(shí)現(xiàn)的,最近改成使用基于 Roslyn 去做了。實(shí)現(xiàn)的原理在于編譯選擇的Model 文件生成一個(gè)程序集,再從這個(gè)程序集中拿到 Model (數(shù)據(jù)庫表)信息以及屬性信息(數(shù)據(jù)庫表字段信息),拿到數(shù)據(jù)庫表以及表字段信息之后就根據(jù)數(shù)據(jù)庫類型生成大致的創(chuàng)建表的 sql 語句。
CodeFirst 效果如下圖所示:
如果你還不知道這個(gè)數(shù)據(jù)庫小工具,歡迎訪問這個(gè)項(xiàng)目了解更多https://github.com/WeihanLi/DbTool
遷移原因
最初的 CodeDom 也是可以用的,但是有一些比較新的 C# 語法不支持,比如 C#6 中的指定屬性初始值 publicintNumber{get;set;}=1;,最初我是遷移到了 Microsoft.CodeDom.Providers.DotNetCompilerPlatform這個(gè)是一個(gè) CodeDom 過渡到 Roslyn 的實(shí)現(xiàn),他提供了和 CodeDom 差不多的語法,支持 C#6 的語法。但是還是有個(gè)問題,我的項(xiàng)目使用了新的項(xiàng)目文件格式,在 VS 中可以編譯通過,但是 dotnet cli 編譯不通過,詳見 issue https://github.com/aspnet/RoslynCodeDomProvider/issues/51
這個(gè)問題已經(jīng)過去一年了仍未解決,最終決定遷移到 Roslyn,直接使用 Roslyn 實(shí)現(xiàn)動(dòng)態(tài)編譯。
對 CodeDom 感興趣的童鞋可以看 DbTool 之前的 commit 記錄,在此不多敘述。
使用 Roslyn 實(shí)現(xiàn)動(dòng)態(tài)編譯
Roslyn 好像沒有直接根據(jù)幾個(gè)文件去編譯(可能有只是我沒發(fā)現(xiàn)),我就使用了一個(gè)比較笨的辦法,把幾個(gè)文件的內(nèi)容都讀出來,合并在一起(命名空間需要去重),然后去編譯,完整源代碼地址,實(shí)現(xiàn)代碼如下:
/// <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()}"; // 創(chuàng)建編譯任務(wù) 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); // 執(zhí)行編譯任務(wù),并輸出編譯后的程序集 if (compilationResult.Success) { // 編譯成功,獲取編譯后的程序集并從中獲取數(shù)據(jù)庫表信息以及字段信息 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()}"); } // 獲取編譯錯(cuò)誤 throw new ArgumentException($"所選文件編譯有錯(cuò)誤{Environment.NewLine}{error}"); }Reference
https://github.com/WeihanLi/DbTool/blob/wfdev/src/DbTool/Utils.cs#L27
https://github.com/WeihanLi/DbTool
https://msdn.microsoft.com/en-us/magazine/mt808499.aspx
總結(jié)
以上是生活随笔為你收集整理的基于 Roslyn 实现动态编译的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 「数据分析」Sqlserver中的窗口函
- 下一篇: [Abp vNext 源码分析] - 4