PAYATHON 2026

Reporting Services 2008 代码创建订阅后未保存

支付老李

结论

代码先调用 CreateBatch,再把返回的 BatchHeader 传给 CreateSubscription。这意味着创建订阅的操作只加入了批处理,并未立即提交。由于最后没有调用 ExecuteBatch,Subscriptions 表中不会出现相应记录。

CreateSubscription 返回 subscription ID,不代表数据库事务已经提交。这个 ID 可以在执行批处理之前生成。

正确的调用顺序是:

  1. 调用 CreateSubscription。
  2. 调用 ExecuteBatch(bh) 提交批处理。
  3. 如果需要立即测试,在批处理提交后单独调用 FireEvent。

建议修改

同时,把报表参数数组从 10 个元素改为实际使用的 3 个元素。原数组剩余的 7 个元素都是 null,可能在序列化或执行批处理时引发错误。

void SendReportEmail()
{
    RSServiceReference.ReportingService2005SoapClient rs =
        new RSServiceReference.ReportingService2005SoapClient();

    rs.ClientCredentials.Windows.AllowedImpersonationLevel =
        System.Security.Principal.TokenImpersonationLevel.Impersonation;

    string batchID;
    RSServiceReference.ServerInfoHeader infoHeader =
        rs.CreateBatch(out batchID);

    RSServiceReference.BatchHeader batchHeader =
        new RSServiceReference.BatchHeader
        {
            BatchID = batchID,
            AnyAttr = infoHeader.AnyAttr
        };

    string report = "/PCMSR6Reports/PaymentRequestStatusMIS";
    string description = "Send report by email";
    string eventType = "TimedSubscription";

    // 必须使用未来时间;下面只是格式示例。
    string scheduleXml =
        "<ScheduleDefinition " +
        "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
        "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
        "<StartDateTime xmlns=\"http://schemas.microsoft.com/sqlserver/2006/03/15/reporting/reportingservices\">" +
        "2026-09-01T15:15:00+08:00" +
        "</StartDateTime>" +
        "</ScheduleDefinition>";

    RSServiceReference.ParameterValue[] extensionParams =
        new RSServiceReference.ParameterValue[]
        {
            new RSServiceReference.ParameterValue
            {
                Name = "TO",
                Value = "Hisham@comsoft.com"
            },
            new RSServiceReference.ParameterValue
            {
                Name = "IncludeReport",
                Value = "True"
            },
            new RSServiceReference.ParameterValue
            {
                Name = "RenderFormat",
                Value = "MHTML"
            },
            new RSServiceReference.ParameterValue
            {
                Name = "Subject",
                Value = "@ReportName was executed at @ExecutionTime"
            },
            new RSServiceReference.ParameterValue
            {
                Name = "Comment",
                Value = "Here is your test report"
            },
            new RSServiceReference.ParameterValue
            {
                Name = "IncludeLink",
                Value = "True"
            },
            new RSServiceReference.ParameterValue
            {
                Name = "Priority",
                Value = "NORMAL"
            }
        };

    RSServiceReference.ParameterValue[] parameters =
        new RSServiceReference.ParameterValue[]
        {
            new RSServiceReference.ParameterValue
            {
                Name = "BranchId",
                Value = "1"
            },
            new RSServiceReference.ParameterValue
            {
                Name = "UserName",
                Value = "admin"
            },
            new RSServiceReference.ParameterValue
            {
                Name = "SupplierId",
                Value = "0"
            }
        };

    RSServiceReference.ExtensionSettings extensionSettings =
        new RSServiceReference.ExtensionSettings
        {
            Extension = "Report Server Email",
            ParameterValues = extensionParams
        };

    try
    {
        string subscriptionId;

        rs.CreateSubscription(
            batchHeader,
            report,
            extensionSettings,
            description,
            eventType,
            scheduleXml,
            parameters,
            out subscriptionId);

        // 关键步骤:真正执行并提交前面加入批处理的操作。
        rs.ExecuteBatch(batchHeader);

        Console.WriteLine(
            "Subscription created: " + subscriptionId);

        // TimedSubscription 本来会由计划触发。
        // 如需立即测试,可在批处理提交后再调用 FireEvent。
        // 具体代理签名可能因生成方式不同而略有差异。
        //
        // rs.FireEvent(null, "TimedSubscription", subscriptionId);
    }
    catch (System.ServiceModel.FaultException ex)
    {
        Console.WriteLine("SOAP fault: " + ex.Message);
        throw;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
        throw;
    }
}

为什么没有出现异常

在批处理模式下,CreateSubscription 只是把操作加入当前 batch。数据库写入和相关验证要等到调用 ExecuteBatch 时才会执行。

因此,缺少 ExecuteBatch 时可能出现以下情况:

  • CreateSubscription 返回了 subscription ID;
  • 调用过程没有出现异常;
  • ReportServer.dbo.Subscriptions 中没有记录;
  • SQL Server Agent 没有可执行的订阅计划;
  • 邮件不会发送。

计划 XML 无效、参数错误或扩展设置不完整等问题,也可能要到执行 ExecuteBatch 时才会暴露。

FireEvent 不能替代 ExecuteBatch

FireEvent 会向报表服务器的事件队列写入事件,用于触发已经存在的订阅。它不能提交 batch,也无法让尚未保存的订阅写入数据库。

调用顺序应为:

CreateBatch
    ↓
CreateSubscription
    ↓
ExecuteBatch
    ↓
可选:FireEvent

如果只需要创建定时订阅,无须调用 FireEvent。计划时间到达后,Reporting Services 会自动触发订阅。

还需要检查的项目

提交批处理后,如果订阅已经写入数据库,但仍未收到邮件,可以继续检查以下内容。

1. 计划时间必须有效

示例中的时间是:

2010-03-06T15:15:00.000+05:00

这个时间已经过去。实际运行时应设置未来时间,并确认时区偏移与报表服务器所在时区一致。否则,即使订阅创建成功,也未必会再次执行。

2. 检查计划 XML 的命名空间

代码使用 ReportingService2005SoapClient,而 ScheduleDefinition 中的命名空间是:

http://schemas.microsoft.com/sqlserver/2006/03/15/reporting/reportingservices

具体使用哪个命名空间,取决于实际连接的 SSRS Web Service 端点,以及生成代理时使用的 WSDL,不能只根据产品名称判断。可以通过以下方式确认:

  • 检查服务引用是否指向正确的 ReportService2005.asmx;
  • 查看生成代理中 ScheduleDefinition 类型的 XML 命名空间;
  • 直接创建 ScheduleDefinition 对象并序列化,避免手写 XML。

如果命名空间或计划结构不符合服务要求,通常会在执行 ExecuteBatch 时收到 SOAP fault。

3. 确认报表参数完整

参数数组只应包含实际使用的元素,不要创建长度为 10 的数组后只赋值 3 项:

new ParameterValue[10]

应改为:

new ParameterValue[3]

还要确认所有必填的报表参数均已提供,并且参数名的大小写和数据格式与报表定义一致。

4. 检查订阅执行状态

订阅创建成功后,可以通过 Web Service 的订阅查询接口或 Report Manager 查看状态。排查时应检查:

  • Subscriptions 中是否存在记录;
  • LastStatus 是否包含发送错误;
  • SQL Server Agent 服务是否正在运行;
  • SSRS 日志中是否有调度或邮件扩展错误;
  • 邮件投递扩展的 SMTP 配置是否有效。

不要直接修改 ReportServer 数据库表。该数据库属于 Reporting Services 的内部实现,应通过 SOAP API 或管理工具操作。

最关键的修复

原代码至少要在 CreateSubscription 后增加:

rs.ExecuteBatch(bh);

如果需要调用 FireEvent,应将它移到 ExecuteBatch 之后。如果只需要按计划发送,则可以直接删除 FireEvent。完成批处理提交后,订阅才会真正写入 ReportServer 数据库。

备注:内容仅供参考。