作者

Sales Engineer at Intersystems
文章 Jeff Liu · 八月 26 7m read

使用 InterSystems IRIS、C# XEP 和 ADO.NET 处理 Gaia 历元光度数据

我在审阅InterSystems 员工编程挑战赛的参赛作品时,突然灵光一闪:何不一边解决这个挑战,一边完成7 月“文章奖励计划”中的一个主题呢? 最终成果是一个紧凑的端到端示例,演示了如何从 C# 调用 InterSystems IRIS,并展示了两个互补的 .NET API:用于高性能对象持久化的 XEP(eXtreme Event Persistence)以及用于查询和分析的 ADO.NET。

在本文中,我们将使用 InterSystems IRIS 和 C# 构建一个完整的数据处理管道。 我们将使用 XEP(eXtreme Event Persistence)API 将 Gaia 纪元光度测量数据导入 IRIS,自动创建持久化模式,通过 ADO.NET 使用 SQL 分析数据,并将结果导出回 CSV 文件。整个过程无需手动编写任何 ObjectScript 类。

 

最终,您将获得一个轻量级的 ETL 应用程序,该应用程序展示了多种 InterSystems 技术的协同工作:

  • 用于高速对象持久化的 XEP
  • ADO.NET 用于 SQL 分析
  • InterSystems IRIS 兼具运营数据库和分析引擎双重功能

数据集

本示例使用了一个包含 Gaia 历元光度观测数据的 CSV 文件。每行代表一个由唯一 source_id 标识的天体。在我们的分析中,我们关注以下两列:

  • bp_flux — 蓝光光度计通量测量值
  • rp_flux — 红光光度计通量测量值

与普通的标量值不同,这些列包含随时间收集的测量值数组。

例如:

bp_flux = [7922.41,7754.46,NaN,8083.01,65199.26]
rp_flux = [6211.03,6120.82,6084.50,6075.42]

现实中的科学数据集很少是完美的,因此数组中可能包含:

  • 缺失值
  • null
  • NaN
  • 无穷大
  • 格式错误的值

该应用程序在计算统计数据时会自动忽略这些值。

应用程序架构

该应用程序遵循四个简单步骤:

  1. 读取 CSV 文件。
  2. 创建持久化 XEP 对象并将其导入 InterSystems IRIS。
  3. 使用 ADO.NET 执行 SQL 查询,以识别波动性较大的数据源。
  4. 将查询结果导出到一个新的 CSV 文件中。

整个工作流由一个 C# 控制台应用程序实现。

定义持久化对象

XEP 最出色的功能之一在于,您无需亲自编写 ObjectScript 持久化类。相反,您只需定义一个普通的 C# 类,XEP 便会自动在 IRIS 内部生成相应的持久化类。这种方法使开发人员能够完全使用 C# 进行开发,同时仍可充分利用 InterSystems IRIS 的持久化和 SQL 功能。

我们的类包含:

  • source_id
  • 最小 BP 通量
  • 最大 BP 通量
  • 最小 RP 通量
  • 最大 RP 通量

在从输入数组中移除所有无效测量值后,会在对象构建过程中计算出最小值和最大值。

namespace Gaia 
{ 
    public sealed class EpochPhotometry 
    { 
        public long source_id; 
        // Stored calculated fields. 
        public double bp_min_flux; 
        public double bp_max_flux; 
        public double rp_min_flux; 
        public double rp_max_flux; 

        internal EpochPhotometry( 
            long sourceId, 
            double[] bpFlux, 
            double[] rpFlux) 
        { 
            source_id = sourceId; 
            bp_min_flux = GetMinimum(bpFlux); 
            bp_max_flux = GetMaximum(bpFlux); 
            rp_min_flux = GetMinimum(rpFlux); 
            rp_max_flux = GetMaximum(rpFlux); 
        } 

        private static double GetMinimum(double[] values) 
        { 
            if (values == null || values.Length == 0) 
            { 
                return 0; 
            } 

            double minimum = double.PositiveInfinity; 
            bool found = false; 

            foreach (double value in values) 
            { 
                if (!double.IsFinite(value)) 
                { 
                    continue; 
                } 

                if (value < minimum) 
                { 
                    minimum = value; 
                    found = true; 
                } 
            } 

            return found ? minimum : 0; 
        } 

        private static double GetMaximum(double[] values) 
        { 
            if (values == null || values.Length == 0) 
            { 
                return 0; 
            } 

            double maximum = double.NegativeInfinity; 
            bool found = false; 

            foreach (double value in values) 
            { 
                if (!double.IsFinite(value)) 
                { 
                    continue; 
                } 

                if (value > maximum) 
                { 
                    maximum = value; 
                    found = true; 
                } 
            } 

            return found ? maximum : 0; 
        } 
    } 
}

程序运行后,您可以看到自动生成的类:

Class Gaia.EpochPhotometry Extends %Library.PersistentXEP [ Final, GeneratedBy = %FOI.Schema, ProcedureBlock ] 
{ 

Parameter IMPORTTIMESTAMP = "19/07/2026 00:32:43"; 

Parameter IMPORTFLAGS = 1; 

Property "source_id" As %Library.BigInt; 

Property "bp_min_flux" As %Library.Double; 

Property "bp_max_flux" As %Library.Double; 

Property "rp_min_flux" As %Library.Double; 

Property "rp_max_flux" As %Library.Double; 

Storage Default 
{ 
<Data name="EpochPhotometryDefaultData"> 
<Value name="1"> 
<Value>source_id</Value> 
</Value> 
<Value name="2"> 
<Value>bp_min_flux</Value> 
</Value> 
<Value name="3"> 
<Value>bp_max_flux</Value> 
</Value> 
<Value name="4"> 
<Value>rp_min_flux</Value> 
</Value> 
<Value name="5"> 
<Value>rp_max_flux</Value> 
</Value> 
</Data> 
<DataLocation>^E6RM.EAvH.1</DataLocation> 
<DefaultData>EpochPhotometryDefaultData</DefaultData> 
<ExtentLocation>^E6RM.EAvH</ExtentLocation> 
<IdLocation>^E6RM.EAvH.1</IdLocation> 
<Index name="IDKEY"> 
<Location>^E6RM.EAvH.1</Location> 
</Index> 
<IndexLocation>^E6RM.EAvH.I</IndexLocation> 
<StreamLocation>^E6RM.EAvH.S</StreamLocation> 
<Type>%Storage.Persistent</Type> 
} 
} 

读取 CSV 文件

该应用程序使用 CsvHelper 库读取 Gaia 数据集。每行数据都会被转换为一个 EpochPhotometry 对象。在解析过程中,程序会:

  • 验证必填列
  • 解析数值数组
  • 忽略无效值
  • 计算最小和最大通量值

如果某个测量值无效,程序不会因此丢弃整个观测数据,而是仅跳过该有问题的数组元素。其余有效值将继续参与计算。

使用 XEP 导入数据

对象创建完成后,应用程序将通过 XEP 连接到 InterSystems IRIS。导入过程仅包含以下几个步骤:

  • 连接到 IRIS
  • 导入模式(如有必要)
  • 清除之前的数据集
  • 分批存储对象
persister.ImportSchema(XepClassName);
irisEvent = persister.GetEvent(XepClassName);
irisEvent.Store(batch);

该应用程序以每批 5,000 个对象为单位存储记录,这大大降低了逐条插入记录的开销。

此时,数据已完全持久化,并可通过 SQL 立即访问。

使用 ADO.NET 进行查询

应用程序的第二部分演示了标准的InterSystems IRIS ADO.NET提供程序。我们不再直接遍历对象,而是使用SQL进行查询。对于每个数据源,我们计算:

percentage_change_bp = ((bp_max_flux - bp_min_flux) / bp_min_flux) * 100
percentage_change_rp = ((rp_max_flux - rp_min_flux) / rp_min_flux) * 100

随后,我们确定哪个百分比更大,并仅保留最大变化率超过100%的来源。由于计算是在SQL内部进行的,因此C#中无需进行额外处理。

查询返回:

  • 源标识符
  • 最小BP通量
  • 最大BP通量
  • 最小 RP 通量
  • 最大 RP 通量
  • BP百分比变化
  • RP百分比变化
  • 最大百分比变化

结果通过受管理的 ADO.NET 提供程序直接从 IRIS 流式传输,并写入一个新的 CSV 文件。

结果

生成的 CSV 文件仅包含根据我们的过滤标准表现出显著变异性的数据源。


运行应用程序

在运行项目之前,请安装:

  • 可访问命名空间的 InterSystems IRIS
  • 与 InterSystems .NET 程序集兼容的 .NET SDK
  • CsvHelper

运行应用程序的机器必须能够访问 IRIS SuperServer 的端口。示例中的默认端口为 `1972`。

在项目目录下,运行:

dotnet add package CsvHelper
dotnet add package InterSystems.Data.IRISClient
dotnet add package InterSystems.Data.XEP

示例项目引用:

 <ItemGroup>
   <PackageReference Include="CsvHelper" Version="33.1.0" />
   <PackageReference Include="InterSystems.Data.IRISClient" Version="2.7.0" />
   <PackageReference Include="InterSystems.Data.XEP" Version="2.5.0" />
 </ItemGroup>

构建项目

dotnet build

运行项目,并将输入和输出 CSV 文件的路径作为命令行参数传递。在 Windows 系统上,也可以使用绝对路径:

dotnet run -- "D:\GitHub\dc-csharp-example\data\in\EpochPhotometry.csv" "D:\GitHub\dc-csharp-example\data\out\EpochPhotometryChanges.csv"

为什么选择 XEP?

尽管 InterSystems IRIS 支持多种持久化方案,但在应用程序已具备丰富的 .NET 对象模型时,XEP 尤为具有吸引力。它允许开发人员:

  • 编写普通的 C# 类
  • 自动生成持久化 IRIS 类
  • 高效存储数千个对象
  • 立即使用 SQL 查询数据
  • 避免编写 ObjectScript 持久化代码

对于ETL作业、科学计算工作负载、遥测以及其他面向对象的数据管道,这使得XEP成为连接.NET应用程序与InterSystems IRIS之间的便捷桥梁。