基于 Roslyn 實現(xiàn)動態(tài)編譯

基于 Roslyn 實現(xiàn)動態(tài)編譯

Intro

之前做的一個數(shù)據(jù)庫小工具可以支持根據(jù) Model 代碼文件生成創(chuàng)建表的 sql 語句,原來是基于 CodeDom 實現(xiàn)的,最近改成使用基于 Roslyn 去做了。實現(xiàn)的原理在于編譯選擇的Model 文件生成一個程序集,再從這個程序集中拿到 Model (數(shù)據(jù)庫表)信息以及屬性信息(數(shù)據(jù)庫表字段信息),拿到數(shù)據(jù)庫表以及表字段信息之后就根據(jù)數(shù)據(jù)庫類型生成大致的創(chuàng)建表的 sql 語句。

CodeFirst 效果如下圖所示:


CodeFirst

如果你還不知道這個數(shù)據(jù)庫小工具,歡迎訪問這個項目了解更多https://github.com/WeihanLi/DbTool

遷移原因

最初的 CodeDom 也是可以用的,但是有一些比較新的 C# 語法不支持,比如 C#6 中的指定屬性初始值 public int Number {get;set;} = 1;,最初我是遷移到了 Microsoft.CodeDom.Providers.DotNetCompilerPlatform
這個是一個 CodeDom 過渡到 Roslyn 的實現(xiàn),他提供了和 CodeDom 差不多的語法,支持 C#6 的語法。但是還是有個問題,我的項目使用了新的項目文件格式,在 VS 中可以編譯通過,但是 dotnet cli 編譯不通過,詳見 issue https://github.com/aspnet/RoslynCodeDomProvider/issues/51

這個問題已經(jīng)過去一年了仍未解決,最終決定遷移到 Roslyn,直接使用 Roslyn 實現(xiàn)動態(tài)編譯。

對 CodeDom 感興趣的童鞋可以看 DbTool 之前的 commit 記錄,在此不多敘述。

使用 Roslyn 實現(xiàn)動態(tài)編譯

Roslyn 好像沒有直接根據(jù)幾個文件去編譯(可能有只是我沒發(fā)現(xiàn)),我就使用了一個比較笨的辦法,把幾個文件的內(nèi)容都讀出來,合并在一起(命名空間需要去重),然后去編譯,完整源代碼地址
,實現(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()}");
    }
    // 獲取編譯錯誤
    throw new ArgumentException($"所選文件編譯有錯誤{Environment.NewLine}{error}");
}

Reference

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容