Table of Contents

高级脚本

本文将介绍 Tabular Editor 的高级脚本功能。 本文档中的信息可能会更改。 另外,也别忘了查看我们的脚本库 @csharp-script-library,里面有更多贴近实际的示例,帮助你了解如何利用 Tabular Editor 的脚本功能完成各种任务。

什么是高级脚本?

Tabular Editor 的界面旨在让你在构建表格模型时,轻松完成大多数常见任务。 例如,要一次性更改多个度量值的显示文件夹,只需在资源管理器树中选中这些对象,然后拖放到目标位置即可。 资源管理器树的右键快捷菜单也提供了便捷方式,可完成许多此类任务,例如将对象添加到透视或从透视中移除、批量重命名对象等。

不过,还有很多常见的工作流任务并不容易通过 UI 来完成。 因此,Tabular Editor 引入了高级脚本,让高级用户可以使用 C# 语法编写脚本,从而更直接地操作已加载的表格模型中的对象。

对象

脚本 API 提供对两个顶层对象的访问:ModelSelected。 前者包含可用于操作表格模型中所有对象的方法和属性;后者则只公开资源管理器树中当前所选的对象。

Model 对象封装了 Microsoft.AnalysisServices.Tabular.Model 类,对外公开其部分属性,并额外提供一些方法和属性,以便更轻松地操作翻译、透视以及对象集合。 其所有后代对象同样如此,例如 Table、度量值、Column 等,都有对应的封装对象。 Tabular Editor 封装库中对象、属性和方法的完整列表,请参阅 Scripting API

通过该封装层进行操作的主要优势是:所有更改都可以在 Tabular Editor UI 中撤销。 脚本执行后只需按 CTRL+Z,你就会看到脚本所做的所有更改会立刻被撤销。 此外,该封装还提供了便捷方法,可将许多常见任务简化为一行代码。 下面我们会给出一些示例。 我们假设读者已对 C# 和 LINQ 有一定了解,因此本文不会介绍这些内容;Tabular Editor 脚本功能中与之相关的部分也不在本文范围内。 即使你不熟悉 C# 和 LINQ,也应该能看懂下面的示例。

设置对象属性

如果你想更改某个特定对象的属性,显然最简单的方式就是直接在 UI 中操作。 不过作为示例,我们来看一下如何通过脚本实现同样的效果。

假设你想更改 'FactInternetSales' 表中 [Sales Amount] 度量值的格式字符串。 在资源管理器树中找到该度量值后,你只需将其拖到脚本编辑器中即可。 接着,Tabular Editor 会生成下面这段代码,用来在 Tabular Object Model 中表示这个特定的度量值:

Model.Tables["FactInternetSales"].Measures["Sales Amount"]

再输入一个点(.) 在最右侧的方括号后输入英文句点“.”,会弹出自动补全菜单,显示该度量值有哪些属性和方法。 在菜单中直接选择 "FormatString",或者输入前几个字母后按 Tab。 然后输入等号 =,再输入 "0.0%"。 我们也把该度量值的显示文件夹一并改一下。 你的最终代码应如下所示:

Model.Tables["FactInternetSales"].Measures["Sales Amount"].FormatString = "0.0%";
Model.Tables["FactInternetSales"].Measures["Sales Amount"].DisplayFolder = "New Folder";

注意: 记得在每行末尾加上分号(;)。 这是 C# 的要求。 如果忘了加,执行脚本时会出现语法错误信息。

按下 F5,或点击脚本编辑器上方的“播放”按钮来执行脚本。 你会立刻看到该度量值在资源管理器树中移动位置,以反映显示文件夹的更改。 如果你在属性网格中查看该度量值,也会看到格式字符串属性已相应更新。

同时处理多个对象

对象模型中的许多对象,其实都是由多个对象组成的集合。 例如,每个 Table 对象都有一个度量值集合。 该包装器为这些集合提供了一系列便捷的属性和方法,方便你一次性为多个对象设置同一个属性。 下面会详细说明。 此外,你还可以使用所有标准的 LINQ 扩展方法来筛选和浏览集合中的对象。

下面是一些最常用的 LINQ 扩展方法示例:

  • Collection.First([predicate]) 返回集合中第一个满足可选 [predicate] 条件的对象。
  • Collection.Any([predicate]) 如果集合包含任意对象(可选:满足 [predicate] 条件),则返回 true。
  • Collection.Where(predicate) 返回按 predicate 条件筛选后的集合。
  • Collection.Select(map) 按照指定的 map,将集合中的每个对象投影为另一个对象。
  • Collection.ForEach(action) 对集合中的每个元素执行指定的 action。

在上面的示例中,predicate 是一个 lambda 表达式:它以单个对象作为输入,并返回一个布尔值作为输出。 例如,如果 Collection 是一个度量值的集合,一个典型的 predicate 可能如下所示:

m => m.Name.Contains("Reseller")

仅当该度量值的 Name 属性包含字符串 "Reseller" 时,此 predicate 才会返回 true。 如果你需要更复杂的逻辑,可以用大括号将表达式括起来,并使用 return 关键字:

.Where(obj => {
    if(obj is Column) {
        return false;
    }
    return obj.Name.Contains("test");
})

回到上面的示例,map 是一个 lambda 表达式:它以单个对象作为输入,并返回一个对象作为输出。 action 是一个 lambda 表达式:它以单个对象作为输入,但不返回任何值。

使用高级脚本编辑器的 IntelliSense 功能查看还有哪些 LINQ 方法,或参考 LINQ-to-Objects 文档

使用 Model 对象

要快速引用当前加载的表格模型中的任意对象,可以将该对象从资源管理器树状视图拖放到高级脚本编辑器中:

将对象拖放到高级脚本编辑器中

如需了解 Model 及其后代对象包含哪些属性,请参阅 TOM 文档。 另外,你也可以查看 Scripting API,获取这个包装对象公开的属性和方法的完整列表。

使用 Selected 对象

在某些工作流中,能够显式引用表格模型中的任意对象非常方便;但有时你希望从资源管理器树中挑选一批对象,然后仅对所选对象执行脚本。 这时 Selected 对象就派上用场了。

Selected 对象提供了一系列属性,便于你识别当前选中了什么,同时还能将选择范围限定为某一特定类型的对象。 在使用显示文件夹浏览时,如果在资源管理器树中选中了一个或多个文件夹,这些文件夹下的所有子项也会被视为已选中。 对于单个对象的选择,请使用要访问的对象类型的单数名称。 例如,

Selected.Hierarchy

它指的是树中当前选中的层次结构,但前提是必须且只能选中一个层次结构。 如果你想处理多选,请使用该类型名称的复数形式:

Selected.Hierarchies

单数形式对象具备的属性,复数形式也同样具备,只有少数例外。 这意味着你可以用一行代码一次性为多个对象设置这些属性值,而无需使用上面提到的 LINQ 扩展方法。 例如,假设你想把当前选中的所有度量值移动到一个名为 "Test" 的新显示文件夹中:

Selected.Measures.DisplayFolder = "Test";

如果树中当前没选中任何度量值,上面的代码什么也不做,也不会引发错误。 否则,所有选中度量值的 DisplayFolder 属性都会被设置为 "Test"(即使这些度量值位于显示文件夹内也一样,因为 Selected 对象也会包含所选显示文件夹中的对象)。 如果你使用单数形式的 Measure 而不是 Measures,除非当前选择中恰好只有一个度量值,否则会报错。

虽然我们无法一次性设置多个对象的 Name 属性,但仍然有一些可选方案。 如果你只是想把某个字符串的所有出现位置替换成另一个字符串,可以使用提供的“Rename”方法,如下所示:

Selected.Measures
        .Rename("Amount", "Value");

这会将当前选中的所有度量值名称中的“Amount”全部替换为“Value”。 或者,我们也可以使用上面介绍的 LINQ ForEach() 方法,以便加入更复杂的逻辑:

Selected.Measures
        .ForEach(m => if(m.Name.Contains("Reseller")) m.Name += " DEPRECATED");

此示例会在所有选中且名称包含 "Reseller" 的度量值名称后追加文本 " DEPRECATED"。 另外,你也可以在应用 ForEach() 操作之前,先用 LINQ 扩展方法 Where() 过滤集合,这将得到完全相同的结果:

Selected.Measures
        .Where(m => m.Name.Contains("Reseller"))
        .ForEach(m => m.Name += " DEPRECATED");

辅助方法

为了让脚本调试更容易,Tabular Editor 提供了一组特殊的辅助方法。 从内部实现来看,这些是用 [ScriptMethod] 特性修饰的静态方法。 该特性允许脚本直接调用这些方法,无需指定命名空间或类名。 插件也可以使用 [ScriptMethod] 特性,以类似方式将公共静态方法公开给脚本调用。

从 2.7.4 起,Tabular Editor 提供以下脚本方法。 注意,其中一些方法也可以作为扩展方法来调用。 例如,object.Output();Output(object); 是等价的。

  • Output(object); - 在弹出对话框中显示指定对象或对象集合的详细信息。 通过 UI 执行时,用户可以选择忽略后续弹窗。 通过 CLI 执行时,信息会输出到控制台。
  • SaveFile(filePath, content); - 将文本数据保存到文件的便捷方式。
  • ReadFile(filePath); - 从文件加载文本数据的便捷方式。
  • ExportProperties(objects, properties); - 将多个对象的一组属性导出为 TSV 字符串的便捷方式。
  • ImportProperties(tsvData); - 从 TSV 字符串将属性导入到多个对象中的便捷方式。
  • CustomAction(name); - 按名称调用自定义操作。
  • CustomAction(objects, name); - 在指定对象上调用自定义操作。
  • ConvertDax(dax, useSemicolons); - 在 US/UK 与非 US/UK 区域设置之间转换 DAX 表达式。 如果 useSemicolons 为 true(默认值),则会将 dax 字符串从 US/UK 的本地格式转换为非 US/UK 格式。 也就是说,逗号(列表分隔符)会转换为分号,句点(小数分隔符)会转换为逗号。 如果将 useSemicolons 设为 false,则相反。
  • FormatDax(IEnumerable<IDaxDependantObject> objects, bool shortFormat, bool? skipSpace) - 格式化所提供集合中所有对象上的 DAX 表达式
  • FormatDax(IDaxDependantObject obj) - 将对象加入 DAX 表达式格式化队列;在脚本执行完成后,或调用 CallDaxFormatter 方法时执行格式化。
  • CallDaxFormatter(bool shortFormat, bool? skipSpace) - 格式化截至目前已入队的对象上的所有 DAX 表达式
  • Info(string); - 在弹出对话框中显示一条信息。 当脚本在 CLI 中运行时,控制台会输出一条信息。
  • Warning(string); - 在弹出对话框中显示一条警告信息。 当脚本在 CLI 中运行时,控制台会输出一条警告信息。
  • Error(string); - 在弹出对话框中显示一条错误信息。 当脚本在 CLI 中运行时,控制台会输出一条错误信息。

你可以在这里查看所有辅助方法的最新列表。

调试脚本

如上所述,你可以使用 Output(object); 方法来暂停脚本执行,并打开一个对话框来显示传入对象的信息。 你也可以将此方法作为扩展方法使用,通过 object.Output(); 来调用。 关闭对话框后,脚本会继续执行。

根据输出对象的类型,对话框会以下面四种方式之一显示:

  • 单个对象(例如 string、int 和 DateTime,但不包括派生自 TabularNamedObject 的任何对象)会通过对该对象调用 .ToString() 方法,以简单的信息对话框形式显示:

image

  • 单个 TabularNamedObject(例如表、度量值,或 Tabular Editor 中提供的任何其他 TOM NamedMetadataObject)会显示在属性网格中,类似于在 Tree Explorer 中选中对象时的效果。 你可以在网格中编辑对象的属性。但请注意:如果在脚本后续执行过程中遇到错误,且启用了“出错时回滚”,这些更改会自动撤销:

image

  • 任何对象的 IEnumerable(不包括 TabularNamedObject)都会以列表形式显示;列表中的每一项都会显示该 IEnumerable 中对象的 .ToString() 值及其类型:

image

  • 任何 TabularNamedObject 的 IEnumerable 都会使对话框左侧显示对象列表,右侧显示属性网格。 属性网格会根据列表中当前选中的对象进行填充;你也可以像输出单个 TabularNamedObject 时一样编辑其属性:

image

你可以勾选左下角的“不再显示更多输出”复选框,以防脚本在后续任何 .Output() 调用时暂停。

.NET 引用

Tabular Editor 2.8.6 版本会让编写复杂脚本变得容易多了。 得益于新的预处理器,你现在可以像在常规 C# 源代码中一样使用 using 关键字来缩短类名等。 此外,你还可以像在 Azure Functions 使用的 .csx 脚本那样,使用语法 #r "<assembly name or DLL path>" 引用外部程序集。

例如,下面的脚本现在将按预期工作:

// Assembly references must be at the very top of the file:
#r "System.IO.Compression"

// Using keywords must come before any other statements:
using System.IO.Compression;
using System.IO;

var xyz = 123;

// Using statements still work the way they're supposed to:
using(var data = new MemoryStream())
using(var zip = new ZipArchive(data, ZipArchiveMode.Create)) 
{
   // ...
}

默认情况下,为了便于完成常见任务,Tabular Editor 会自动添加以下 using 指令(即使脚本中未显式声明):

using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using TabularEditor.TOMWrapper;
using TabularEditor.TOMWrapper.Utils;
using TabularEditor.UI;

此外,默认还会加载以下 .NET Framework 程序集:

  • System.Dll
  • System.Core.Dll
  • System.Data.Dll
  • System.Windows.Forms.Dll
  • Microsoft.Csharp.Dll
  • Newtonsoft.Json.Dll
  • TomWrapper.Dll
  • TabularEditor.Exe
  • Microsoft.AnalysisServices.Tabular.Dll

使用 Roslyn 进行编译

如果你更喜欢使用 Visual Studio 2017 引入的新 Roslyn 编译器来编译脚本,那么从 Tabular Editor 2.12.2 版本开始,你可以在“文件 > 偏好设置 > 常规”中进行配置。 这样你就可以使用更新的 C# 语言特性,比如字符串插值。 只需指定包含编译器可执行文件(csc.exe)的目录路径,并在编译器选项中指定语言版本:

image

Visual Studio 2017

对于典型的 Visual Studio 2017 Enterprise 安装,Roslyn 编译器位于此处:

c:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\Roslyn

默认包含 C# 6.0 的语言特性。

image

Visual Studio 2019

对于典型的 Visual Studio 2019 Community 安装,Roslyn 编译器位于:

c:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\Roslyn

VS2019 随附的编译器支持 C# 8.0 语言特性,可通过在编译器选项中指定以下内容来启用:

-langversion:8.0

Visual Studio 2022

对于典型的 Visual Studio 2022 Community Edition 安装,Roslyn 编译器位于:

C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\Roslyn\csc.exe

如果你使用的是 Visual Studio 2022 的其他版本,路径可能会略有不同。 例如,对于 企业版,路径位于:

C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Roslyn

VS2022 最新更新随附的编译器支持 C# 12.0 语言特性,可通过在编译器选项中指定以下内容来启用:

-langversion:12.0