您当前的位置: 首页 >  visual studio

寒冰屋

暂无认证

  • 0浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

在Visual Studio中使用C#脚本(CSX脚本)生成代码

寒冰屋 发布时间:2020-09-03 22:50:32 ,浏览量:0

目录

介绍

C#脚本(CSX文件)

CSX脚本示例

MyProgram.cs

MyScript.csx

使用C#REPL(CSI.EXE)运行CSX脚本

程序集引用

NuGet软件包

从PowerShell调用C#REPL(以运行CSX脚本)

从Visual Studio IDE运行

允许未签名的脚本

相关程序集引用

PackageReference(NuGet 4)与packages.config(NuGet 3)

创建一个简单的POCO生成器

介绍

CSX脚本非常强大,因为它们可以使用C#和完整的.NET Framework。在这篇文章中,我将一步一步地展示运行CSX脚本(c#脚本)的指令和脚本,以及对外部程序集的引用(第三方库,包括NuGet包),使用可以直接从Visual Studio调用的Powershell脚本。我还将展示一些技巧,以使脚本在整个开发团队中无需修改即可工作。最后,我将使用一个简单的代码生成库来基于AdventureWorks数据库模式生成POCO 。

C#脚本(CSX文件)

C#脚本文件(CSX)是Roslyn引入的,可以在Roslyn或其他兼容的跨平台脚本引擎(如dotnet-script)中执行,甚至可以在C#REPL(称为csi.exe)中执行。

这些脚本引擎有一些局限性(例如缺少名称空间),但是它们允许我们虚拟地调用任何C#代码,这为我们提供了一些令人惊奇的功能,例如强类型,编译时检查,完整的IDE支持(带有调试功能),跨平台(dotnet core),对所有.NET Framework的完全访问权限(不仅包括SqlServer库,还包括一些了不起的第三方库,例如Dapper,Newtonsoft JSON和别的)。因此,如果我们谈论的是自动化,那么我们将获得具有熟悉语法的全面语言,而不必依赖于PowerShell。而且,如果我们谈论的是代码生成,那么我们还将获得一种具有熟悉语法的成熟语言,而不是依赖仅提供基础语言功能子集的模板引擎。

CSX脚本示例

Visual Studio中的CSX脚本对Intellisense(自动完成)和编译时检查提供了一些支持,但是这些功能在CS文件中的工作要好得多。因此,最好在cs文件中放入尽可能多的内容,而在CSX脚本中放入尽可能少的内容。我只想将CSX用于基本功能,例如加载库,设置连接字符串,设置路径以及调用CS文件中的实际代码。

MyProgram.cs
public class MyProgram
{
   public void MyMethod()
   {
      Console.WriteLine("Hello from MyMethod");
   }  
}
MyScript.csx
#load "MyProgram.cs" 

new MyProgram().MyMethod(); 
Console.WriteLine("Hello Code-Generation!");
使用C#REPL(CSI.EXE)运行CSX脚本

Visual Studio附带了一个称为CSI的命令行REPL,可用于运行.csx脚本。

您可以直接从Visual Studio开发人员命令提示符(csi MyScript.csx)运行CSI.EXE:

程序集引用

从相同的意义上讲,在CSX中使用简单的语句来调用更复杂的CS代码是一个好主意,在您可以依赖现有库时加载外部程序集也是一个好主意。

CSX允许通过使用脚本顶部的#r指令来加载程序集引用:

// CSI.EXE requires absolute paths for loading external assemblies: 
#r "C:\Users\drizin\.nuget\packages\dapper\2.0.35\lib\netstandard2.0\Dapper.dll" 

#load "File1.cs" 
#load "File2.cs" 
#load "MyProgram.cs" 

new MyProgram().MyMethod(); 
Console.WriteLine("Hello Code-Generation!");
NuGet软件包

如果需要引用NuGet程序包,则可以仅依靠NuGet工具(和Visual Studio生成过程)来自动还原脚本所需的程序包。为此,您只需将CSX添加为Visual Studio项目的一部分,因此,当每个开发人员尝试构建该项目时,Visual Studio都会下载缺少的包,而开发人员只需要修复程序集的位置即可。

从PowerShell调用C#REPL(以运行CSX脚本)

尽管您可以直接从Visual Studio开发人员命令提示符运行CSI.exe,但出于以下几个原因,通过PowerShell调用它非常有用:

  • 您可以在Visual Studio之外运行。您甚至不需要Visual Studio来运行CSX。
  • PowerShell允许我们使用相对路径引用外部程序集(有关此内容的更多信息,请参见下文)。

要使用Powershell调用CSI,我们必须知道csi.exe的位置。

CSI随Visual Studio一起提供,但是根据您的Visual Studio版本,可能有不同的文件夹。即使您没有Visual Studio,仍然可以使用NuGet包Microsoft.Net.Compilers.Toolset安装CSI 。

因此,第一步是在多个位置搜索csi.exe,如我在下面的示例Powershell脚本RunMyScript.ps1中 所示:

# Locate CSI.EXE by searching common paths 
$csi = (
  "$Env:userprofile\.nuget\packages\microsoft.net.compilers.toolset\3.6.0\tasks\net472\csi.exe", 
  "$Env:programfiles 
   (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Roslyn\csi.exe", 
  "$Env:programfiles 
   (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\Roslyn\csi.exe",
  "$Env:programfiles 
   (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\Roslyn\csi.exe",
  "$Env:programfiles 
   (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\Roslyn\csi.exe",
  "$Env:programfiles 
   (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Roslyn\csi.exe",
  "$Env:programfiles 
   (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\Roslyn\csi.exe" 
) | Where-Object { Test-Path $_ } | Select-Object -first 1 

$dir = Split-Path $MyInvocation.MyCommand.Path 
$script = Join-Path $dir "MyScript.csx" 

& $csi $script

要从命令行启动PowerShell脚本,只需运行:

Powershell Full-Path-To-Your-Script-ps1
从Visual Studio IDE运行

要从Visual Studio运行,您只需将PS1添加到您的项目或解决方案中,右键单击该文件,然后单击选项“使用PowerShell ISE打开”,这是用于编辑/运行PowerShell脚本的IDE。

另一种选择是,您可以向右键单击操作中添加新操作-您可以单击“打开方式...”,并将PowerShell配置为直接从Visual Studio执行:

可能的操作列表将包括此直接从IDE调用PS1脚本的新选项,您还可以将其设置为打开PS1文件的默认操作。

允许未签名的脚本

如果您从未执行过未签名的PowerShell脚本,则可能必须通过以管理员身份运行PowerShell(x64版本和x86版本,这是从Visual Studio内部执行的)来启用PowerShell未签名的脚本,然后运行以下命令:

Set-ExecutionPolicy -ExecutionPolicy Unrestricted
相关程序集引用

CSI的主要问题之一是#r指令(用于加载程序集引用的)不接受 类似NuGet的引用或环境变量,因此所有程序集引用都应使用完整路径指定。这不是一个大问题,但是有点烦人,因为这使得在多个开发人员之间共享代码更加困难,因为每个开发人员都必须修复他们的引用。

正如我们之前所见,CSX期望这样的绝对引用:

#r "C:\Users\drizin\.nuget\packages\dapper\2.0.35\lib\netstandard2.0\Dapper.dll"

使用PowerShell的优势之一(如上所述)是我们可以使用环境变量,并在相对路径中使用#r指令。在PowerShell脚本中,我们只需要找到程序集所在的基本路径并将其传递给CSI,以便它可以在此文件夹中搜索程序集,如下所示:

$assemblies = "${env:userprofile}\.nuget\packages\";
& $csi /lib:"$assemblies" $script

然后在CSX中,您可以使用类似的相对路径:

#r "dapper\2.0.35\lib\netstandard2.0\Dapper.dll"
PackageReference(NuGet 4)与packages.config(NuGet 3)

新的MSBuild格式(在csproj内部使用的PackageReference “SDK样式” )会将NuGet软件包安装在此每个用户文件夹中。

旧的MSBuild格式(使用packages.config的Visual Studio 2017之前的“非SDK样式” )将NuGet程序包安装在Solution文件夹下的“ packages ”文件夹中。

我们可以根据项目在哪里还原NuGet软件包来调整PowerShell脚本:

$csi = ... # (locate your csi.exe)
$dir = Split-Path $MyInvocation.MyCommand.Path 
$script = Join-Path $dir "MyScript.csx"

# Call csi.exe and specify that libraries referenced by #r directives 
# should search in a few nuget locations

# New NuGet 4.0+ (PackageReference) saves User-specific packages
# in "%userprofile%\.nuget\packages\"
$nuget1 = "${env:userprofile}\.nuget\packages\";

# New NuGet 4.0+ (PackageReference) saves Machine-wide packages 
# in "%ProgramFiles(x86)%\Microsoft SDKs\NuGetPackages\"
$nuget2 = "${env:ProgramFiles(x86)}\Microsoft SDKs\NuGetPackages\";

# Old NuGet (packages.config) saves packages in "\packages" folder at solution level.
# Locate by searching a few levels above
$nuget3 = ( 
    (Join-Path $dir ".\packages\"),
    (Join-Path $dir "..\packages\"),
    (Join-Path $dir "..\..\packages\"),
    (Join-Path $dir "..\..\..\packages\"),
    (Join-Path $dir "..\..\..\..\packages\")
) | Where-Object { Test-Path $_ } | Select-Object -first 1

# if you're using new NuGet format (PackageReference defined inside csproj) 
& $csi /lib:"$nuget1" $script  

# if you're using old NuGet format (packages.config)
# & $csi /lib:"$nuget3" $script

我们的CSX将使用相对引用:

// CSX can load libraries by defining their relative paths

// New NuGets (PackageReference) are installed under "${env:userprofile}\.nuget\packages\" 
// or "${env:ProgramFiles(x86)}\Microsoft SDKs\NuGetPackages\")
// and have this format:
#r "dapper\2.0.35\lib\netstandard2.0\Dapper.dll"

// Old NuGets (packages.config) are installed under "(SolutionFolder)\packages"
// and have this format
// #r "Dapper.2.0.35\lib\netstandard2.0\Dapper.dll"

//...
new MyProgram().MyMethod();
Console.WriteLine("Hello Code-Generation!");

很酷又很容易,不是吗?

创建一个简单的POCO生成器

到目前为止,这篇文章是在我的博客中与该文章交叉发布的。为了简洁起见,我将跳过一些步骤,但是在另一篇文章中,我创建了一个工具来从SQL数据库提取架构并将其另存为JSON文件。

基于此JSON模式并使用CodegenCS代码生成器库,我们可以轻松生成POCO:

public class SimplePOCOGenerator
{
  CodegenContext generatorContext = new CodegenContext();

  public void Generate()
  {
     DatabaseSchema schema = JsonConvert.DeserializeObject(
        File.ReadAllText(_inputJsonSchema));

     foreach (var table in schema.Tables)
        GeneratePOCO(table);

     // This saves one .cs for each table
     generatorContext.SaveFiles(outputFolder: targetFolder);

     // This will add each .cs to our csproj file (if using old format)
     //generatorContext.AddToProject(csProj, targetFolder);
  }

  void GeneratePOCO(DatabaseTable table)
  {
      var writer = generatorContext[table.TableName + ".cs"];
      writer
          .WriteLine(@"using System;")
          .WriteLine(@"using System.Collections.Generic;")
          .WriteLine(@"using System.ComponentModel.DataAnnotations;")
          .WriteLine(@"using System.ComponentModel.DataAnnotations.Schema;")
          .WriteLine(@"using System.Linq;")
          .WriteLine();

      writer.WithCBlock($"namespace {myNamespace}", () =>
      {
         writer.WithCBlock($"public partial class {table.TableName}", () =>
         {
             foreach (var column in table.Columns)
                GenerateProperty(writer, table, column);
         });
      });
  }

    void GenerateProperty(CodegenOutputFile writer, DatabaseTable table, 
                          DatabaseTableColumn column)
    {
        string propertyName = GetPropertyNameForDatabaseColumn(table, column.ColumnName);
        string typeDefinition = GetTypeDefinitionForDatabaseColumn(table, column);
        if (column.IsPrimaryKeyMember)
            writer.WriteLine("[Key]");
        if (propertyName.ToLower() != column.ColumnName.ToLower())
            writer.WriteLine($"[Column(\"{column.ColumnName}\")]");
        writer.WriteLine($"public {typeDefinition} {propertyName} {{ get; set; }}");
    }
}

最终结果是每个表一个POCO:

您最喜欢的micro-ORM可以使用POCO:

很酷又很容易,不是吗?希望您和我一样喜欢这篇文章!

本文的完整源代码可以下载(在顶部),并且也已在此处发布。

代码包含用于SDK风格和非SDK风格项目的CSX和PowerShell脚本:

  • Visual Studio 2017+(SDK风格的项目,dotnetcore也使用)以SDK风格的格式,NuGet包按用户配置文件存储
  • Visual Studio
关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0943s