基于 Roslyn 实现动态编译

640?wx_fmt=png

基于 Roslyn 实现动态编译

Intro

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

CodeFirst 效果如下图所示:640?wx_fmt=png

如果你还不知道这个数据库小工具,欢迎访问这个项目了解更多https://github.com/WeihanLi/DbTool

迁移原因

最初的 CodeDom 也是可以用的,但是有一些比较新的 C# 语法不支持,比如 C#6 中的指定属性初始值 publicintNumber{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

  • 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

    640?wx_fmt=png

640?wx_fmt=jpeg


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值