zoukankan      html  css  js  c++  java
  • 基于 Roslyn 实现动态编译

    基于 Roslyn 实现动态编译

    Intro

    之前做的一个数据库小工具可以支持根据 Model 代码文件生成创建表的 sql 语句,原来是基于 CodeDom 实现的,最近改成使用基于 Roslyn 去做了。实现的原理在于编译选择的Model 文件生成一个程序集,再从这个程序集中拿到 Model (数据库表)信息以及属性信息(数据库表字段信息),拿到数据库表以及表字段信息之后就根据数据库类型生成大致的创建表的 sql 语句。

    CodeFirst 效果如下图所示:
    Code First

    如果你还不知道这个数据库小工具,欢迎访问这个项目了解更多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

  • 相关阅读:
    数据挖掘-基本流程
    ArcGIS GP应用-GP模型服务发布
    ArcGIS GP应用-GP模型创建-缓冲区分析
    Hadoop2的Yarn和MapReduce2相关
    hadoop学习WordCount+Block+Split+Shuffle+Map+Reduce技术详解
    WordCount示例深度学习MapReduce过程
    数组的几种排序算法的实现
    hBase官方文档以及HBase基础操作封装类
    Hive SQL执行流程分析
    Hive SQL的编译过程
  • 原文地址:https://www.cnblogs.com/weihanli/p/dynamic-compile-via-roslyn.html
Copyright © 2011-2022 走看看