PAYATHON 2026

SQL Server 2022 中 EzApi 创建和保存 SSIS 包报错,如何解决?

排障赵师傅

结论

先保留 Microsoft.Pipeline、Microsoft.ExecuteSQLTask 等原有标识,排查 SSIS 运行环境、程序集加载和组件注册。现有现象不足以证明 SQL Server 2022 要求将这些 moniker 全部替换为完整程序集名称或带 .8 的标识。

下面这行原生 API 调用也会失败:

pkg0.Executables.Add("Microsoft.Pipeline");

说明不使用 EzApi 时,故障同样会出现。应先用原生 API 完成创建、保存和重新加载包的最小测试,再调整 EzApi。

目前还无法确认以下判断:

  • 0x80070057 的根因一定是 moniker 变化。
  • Microsoft.SqlServer.PipelineXML 要求使用另一套保存专用 moniker。
  • Microsoft 因安全原因禁止普通应用程序使用 ScriptTask DLL。

题目没有提供最终修复结果,因此无法断言替换某个 DLL 或修改某个字符串就能解决问题。

为什么替换 moniker 后仍然会失败

SSIS 创建任务需要解析任务名称、加载程序集,并创建原生组件。将短名称改成完整程序集名称,可能让某个任务通过另一条加载路径创建成功。但这既不能证明原来的名称已经失效,也不能说明后续依赖都已正确加载。

如果空 Data Flow 能保存,加入组件后却无法保存,就需要检查加入组件后触发的处理,包括组件持久化、依赖加载和版本一致性。仅凭异常涉及哪个 DLL,还无法判断根因。

这两个错误需要分别排查:

错误当前能确定的范围
Cannot create a task from XML ... 0x80070057任务创建或初始化失败;错误码本身不能指明哪个名称、文件或依赖有问题
Failed to save Data Flow objects保存数据流对象时失败;需要查看更具体的内部异常和 SSIS 错误信息

0x80070057 对应参数错误,但这里的“参数”未必就是传给 Executables.Add 的字符串。

先核对实际加载的 SSIS 环境

检查安装与进程位数

确认生成程序所在的机器安装了所需的 SQL Server 2022 Integration Services 运行组件。仅安装数据库引擎、SSMS,或只引用几个 DLL,都不能替代完整的 SSIS 依赖环境。

还需要检查:

  • 生成程序实际运行在 x86 还是 x64。
  • 对应位数的 SSIS 组件及相关提供程序是否存在。
  • 是否同时安装了多个 SQL Server 或 SSIS 版本。
  • 应用程序输出目录是否残留 SQL Server 2016 等旧版本的 DLL。
  • 是否引用了 Visual Studio 扩展目录中的设计时文件,却加载了另一套运行时文件。

排查实际加载情况时,调试器的“模块”窗口比项目的“引用”列表更有用:引用列表显示编译时使用的文件,模块窗口显示进程实际加载的文件。

可以先输出以下信息:

using System;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;

Console.WriteLine("64-bit process: " + Environment.Is64BitProcess);

Console.WriteLine(typeof(Application).Assembly.FullName);
Console.WriteLine(typeof(Application).Assembly.Location);

Console.WriteLine(typeof(MainPipe).Assembly.FullName);
Console.WriteLine(typeof(MainPipe).Assembly.Location);

这些输出只覆盖对应的托管程序集或互操作程序集。原生 SSIS DLL 的实际加载路径和文件版本,还需要在模块窗口中检查。

核对环境是否一致时,要一起检查程序集版本、文件版本和实际加载路径,不能只看 Version=16.0.0.0。

检查目标版本

如果使用 SSIS 项目做对照测试,应确认项目的 TargetServerVersion 为 SQL Server 2022。生成程序使用的 SDK 如果提供目标版本设置,也要核对。

Visual Studio 能成功保存包,说明它自身的进程和依赖环境可以完成操作,并不能证明独立 C# 程序加载了同一套组件。

如何检查 moniker

使用 Application.TaskInfos 检查任务,使用 Application.PipelineComponentInfos 检查数据流组件:

var app = new Microsoft.SqlServer.Dts.Runtime.Application();

foreach (var task in app.TaskInfos)
{
    Console.WriteLine(
        $"Task: {task.Name}, CreationName: {task.CreationName}");
}

foreach (var component in app.PipelineComponentInfos)
{
    Console.WriteLine(
        $"Component: {component.Name}, CreationName: {component.CreationName}");
}

这些代码必须在发生故障的生成程序中执行,结果才反映该进程看到的组件信息。

使用这些名称时,需要区分:

  • Executables.Add(...) 接收任务的创建标识。
  • ComponentClassID 用于指定数据流组件,不能直接套用任务的程序集名称写法。
  • 不能仅凭 .5、.8 等后缀中的数字推断 SQL Server 产品版本。
  • 能枚举到组件,不代表它一定能完成实例化、保存和重新加载。

可以把枚举得到的 CreationName 作为诊断候选,与原有标识分别测试。每次测试都使用新进程和新包,并记录成功到了哪一步。批量替换会让结果失去对照依据。

用原生 API 分阶段复现

先移除 EzApi 和 ScriptTask,只保留 Data Flow。下面的示例用于判断保存空数据流时是否失败,以及故障是否在加入 OLE DB Source 后才出现。它尚未被验证为适用于所有 SQL Server 2022 安装环境的修复代码。

using System;
using System.IO;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;

sealed class DiagnosticEvents : DefaultEvents
{
    public override bool OnError(
        DtsObject source,
        int errorCode,
        string subComponent,
        string description,
        string helpFile,
        int helpContext,
        string idofInterfaceWithError)
    {
        Console.Error.WriteLine(
            $"0x{unchecked((uint)errorCode):X8} " +
            $"[{subComponent}] {description}");

        return false;
    }
}

static class Program
{
    static void Main()
    {
        var app = new Application();
        var events = new DiagnosticEvents();
        var package = new Package
        {
            Name = "TestPackage"
        };

        try
        {
            Console.WriteLine("Creating Data Flow");

            var host =
                (TaskHost)package.Executables.Add("Microsoft.Pipeline");

            host.Name = "df0";

            string emptyPath =
                Path.Combine(Path.GetTempPath(), "ssis-empty-flow.dtsx");

            app.SaveToXml(emptyPath, package, events);
            Console.WriteLine("Saved: " + emptyPath);

            Console.WriteLine("Adding OLE DB Source");

            var dataFlow = (MainPipe)host.InnerObject;
            var metadata = dataFlow.ComponentMetaDataCollection.New();

            metadata.ComponentClassID = "Microsoft.OLEDBSource";

            IDTSDesigntimeComponent100 instance = metadata.Instantiate();
            instance.ProvideComponentProperties();

            metadata.Name = "TestOleDbSource";
            metadata.ValidateExternalMetadata = false;

            string componentPath =
                Path.Combine(Path.GetTempPath(), "ssis-with-source.dtsx");

            app.SaveToXml(componentPath, package, events);
            Console.WriteLine("Saved: " + componentPath);
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex.ToString());

            foreach (DtsError error in package.Errors)
            {
                Console.Error.WriteLine(
                    $"0x{unchecked((uint)error.ErrorCode):X8} " +
                    error.Description);
            }

            throw;
        }
    }
}

这个 OLE DB Source 尚未配置连接和查询,不能作为可执行的数据抽取包。测试只覆盖创建和持久化阶段,不涉及执行阶段。

根据失败位置继续排查:

  1. Executables.Add 失败:优先检查任务名称解析、运行时加载、安装与注册。
  2. 空 Data Flow 保存成功,Instantiate 或 ProvideComponentProperties 失败:检查具体组件及其依赖。
  3. 加入组件后才保存失败:检查完整事件日志、组件持久化相关依赖,以及是否混用了不同版本的文件。
  4. 保存成功:通过 Application.LoadPackage 重新加载,再另行配置连接、验证和执行。

DelayValidation 和 ValidateExternalMetadata = false 无法修复程序集加载、组件注册或序列化故障。

如何定位 Failed to save Data Flow objects

排查时需要收集以下信息,仅有最外层的异常消息还不够:

  • ex.ToString() 中的内部异常和完整调用堆栈。
  • 保存时传入的 IDTSEvents 回调信息。
  • Package.Errors 中的错误。
  • 故障进程实际加载的 DLL 路径和版本。

如果这些信息仍不足以定位问题,可以继续检查:

  • 使用程序集绑定日志检查托管程序集绑定失败的情况。
  • 使用 Process Monitor 检查故障时是否查找了错误版本的 DLL,或访问了不存在的文件、注册表项。

判断是否缺少依赖、加载了旧版本或安装注册不完整,需要这些诊断结果支持。调用堆栈中出现 Microsoft.SqlServer.PipelineXML,只能说明失败发生在相关调用过程中。

如果确认安装或注册有问题,应使用对应版本的安装程序修复。没有明确依据时,不要手工复制系统 DLL、修改注册表,或反复尝试注册不同版本的组件。

运行时与保存时是否使用不同 moniker

创建接口接受的标识与 DTSX 中最终持久化的标识,不一定逐字相同。二者有差异,并不足以说明必须在保存前切换 moniker。

Visual Studio 生成的 XML 仍使用原有名称,也不能证明 PipelineXML 只接受旧名称。还需要在相同目标版本下比较实际加载的环境,并检查包能否完成保存和重新加载。

不建议在保存前手工改写 CreationName 或 ComponentClassID,也不建议通过修改 DTSX 字符串绕过当前异常。

原生 API 测试通过后,再调整 EzApi:先统一引用和依赖来源,再逐项验证任务与组件标识。某项替换只有在创建、保存、重新加载及后续验证中都能稳定通过,才适合保留在兼容性代码中。

ScriptTask 的异常需要单独处理

下面的异常涉及 VSTA 宿主和路径布局:

Except devenv, all applocalized program like dtdebughost.exe
should be under CommonExtensions folder

这条异常不能证明 Microsoft 因代码安全问题禁止第三方程序使用 ScriptTask DLL。

需要单独检查 VSTA、SSIS 设计工具版本、ProjectTemplatePath,以及程序加载的设计时依赖。即使任务实例创建成功,独立程序也未必具备创建、编译脚本项目所需的完整环境。

可以暂时将 ScriptTask 移出最小复现。删除 LoadNewProject 并不代表脚本任务已经适配成功,后续仍需验证脚本项目、编译产物和包加载行为。

备注:内容仅供参考。