可变数量的参数 C# Interop

Variable number of Parameters C# Interop

有一个 dtsx 包,运行s SSRS 使用配置 table 报告,然后在生成的 .xls(它是 2008 R2 服务器)文件上需要时执行宏.

配置的每一行 table 包含要 运行 的报告的详细信息、宏的详细信息以及要传递给宏的参数和值的 xml 列表;包中有一个 foreach 循环,运行s 报告,向输出文件添加一个宏,运行s 宏并继续。

看起来好像是为了尽可能通用化而设计的,所以任何报告和任何宏都可以在配置中输入table。

这工作正常,但尽管它看起来很灵活,但加载的脚本代码中的实际 C# 和 运行s 宏具有非常严格的结构,这意味着只有宏具有4 个参数可以是 运行 - 我想让它更灵活,但我正在努力研究如何根据节点的数量为 ExcelObject.Run() 命令提供正确(可变)的参数数量配置 xml.

我在使用 C# 方面不是很有经验,但从阅读 this Microsoft and this Whosebug 文章看来,我可以制作一个参数数组并传递它...我只是不知道如何做。这是现有的脚本:

using System;
using System.IO;
using System.Xml;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Text;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.SqlServer.Dts.Runtime;
using System.Reflection;
using VBIDE = Microsoft.Vbe.Interop;


namespace ST_b2148758d9a44ee4bc0d01a2d900ce9d.csproj
{
    [System.AddIn.AddIn("ScriptMain", Version = "1.0", Publisher = "", Description = "")] 



    public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase 
    {
        enum ScriptResults
        {
            Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
            Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
        };

        public void Main()
        {
            Variables UserVariables = null;
            Dts.VariableDispenser.LockForRead("User::SaveLoc_Root");
            Dts.VariableDispenser.LockForRead("User::SaveLoc_SubFolder");
            Dts.VariableDispenser.LockForRead("User::SaveLoc_FileName");
            Dts.VariableDispenser.LockForRead("User::SaveLoc_FileExtension");
            Dts.VariableDispenser.LockForRead("User::VBA_Macro_Name");
            Dts.VariableDispenser.LockForRead("User::VBA_Parameters");
            Dts.VariableDispenser.LockForRead("User::VBA_Script");
            Dts.VariableDispenser.GetVariables(ref UserVariables);
            string SaveToLocation = null, ConstantName = null, DateFormat = null , SheetNameInCell = null;

            //build the filename of the report we just made. 
            //@"C:\Temp\Repart.xls";
            string Report_File_name = UserVariables["User::SaveLoc_Root"].Value.ToString()
                + UserVariables["User::SaveLoc_SubFolder"].Value.ToString()
                + UserVariables["User::SaveLoc_FileName"].Value.ToString()
                + UserVariables["User::SaveLoc_FileExtension"].Value.ToString();

            //The macro and the macro name were stored in the original data set, so we can get those from local variables.
            string Macro = UserVariables["User::VBA_Script"].Value.ToString();
            string Macro_name = UserVariables["User::VBA_Macro_Name"].Value.ToString();

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(UserVariables["User::VBA_Parameters"].Value.ToString());
            XmlNodeList Parameters = doc.GetElementsByTagName("Parameter");

            //skip through all the parameters we might have - if more are added, this will need to be changed.
            for (int i = 0; i < Parameters.Count; i++)
            {
                string ParamName = Parameters[i].Attributes["Name"].Value.ToString();
                if (ParamName == "SaveToLocation")
                {
                    SaveToLocation = Parameters[i].Attributes["Value"].Value.ToString();
                }
                else if (ParamName == "ConstantName")
                {
                    ConstantName = Parameters[i].Attributes["Value"].Value.ToString();
                }
                else if (ParamName == "DateFormat")
                {
                    DateFormat = Parameters[i].Attributes["Value"].Value.ToString();
                }
                else if (ParamName == "SheetNameInCell")
                {
                    SheetNameInCell = Parameters[i].Attributes["Value"].Value.ToString();
                }
            }
            //Get Excel ready to be opened
            Excel.Application ExcelObject = default(Excel.Application);
            Excel.WorkbookClass oBook = default(Excel.WorkbookClass);
            Excel.Workbooks oBooks = default(Excel.Workbooks);
            //get the vba module ready
            VBIDE.VBComponent module = null;

            //open excel in the background
            ExcelObject = new Excel.Application();
            ExcelObject.Visible = false;

            //Open our report
            oBooks = ExcelObject.Workbooks;
            oBook = (Excel.WorkbookClass)oBooks.Open
                (Report_File_name
                 ,Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value,Missing.Value,Missing.Value
                 ,Missing.Value,Missing.Value,Missing.Value,Missing.Value ,Missing.Value,Missing.Value);

            //Add a module to our report and populate it with our vba macro
            module = oBook.VBProject.VBComponents.Add(VBIDE.vbext_ComponentType.vbext_ct_StdModule);
            module.CodeModule.AddFromString(Macro);

            //run the macro
            ExcelObject.Run
                   ( Macro_name,SaveToLocation,ConstantName,DateFormat,SheetNameInCell, Missing.Value, Missing.Value, Missing.Value,Missing.Value
                    ,Missing.Value, Missing.Value, Missing.Value,Missing.Value, Missing.Value, Missing.Value, Missing.Value,Missing.Value
                    ,Missing.Value, Missing.Value, Missing.Value,Missing.Value, Missing.Value, Missing.Value, Missing.Value,Missing.Value
                    ,Missing.Value, Missing.Value, Missing.Value,Missing.Value, Missing.Value, Missing.Value);


            ExcelObject.Visible = false;
            ExcelObject.UserControl =false;
            //oBook.Save();

            ExcelObject.DisplayAlerts =false;
            ExcelObject.Application.Quit();
            ExcelObject =null;
        }
    }
}

xml 看起来像这样(我希望能够添加更多或更少的参数,而不会失败 - 也就是说,我不介意参数的最大数量):

<VBAParameters>
  <Notes>The source spreadsheet is produced by the SSIS package "Reports.dtsx" which runs on SQL7. The settings here will be used to split that workbook into several files.
*SaveToLocation is where the script will save the each departments report (each sheet from the source workbook)
*ConstantName is the text that will appear in all files, along with the date and the department name
*DateFormat is the format of the date that will appear in the filename
*SheetNameInCell gives the address of the cell that in each sheet of the source spreadsheet contains the department name.
    </Notes>
  <Parameters>
    <Parameter Name="SaveToLocation" Value="C:\Temp" />
    <Parameter Name="ConstantName" Value="Post Summary" />
    <Parameter Name="DateFormat" Value="yyyyMM" />
    <Parameter Name="SheetNameInCell" Value="A5" />
  </Parameters>
</VBAParameters>

任何关于我如何能够将必要数量的非 missing.value 参数传递给宏的指导都将非常感激。因此,我的努力是徒劳的。幸运的是 xml reader 按谓词 table 顺序执行 return 参数,因此不需要名称。

关于你的问题

it seems from reading this Microsoft and this Whosebug article that I can make an array of parameters and pass this...

不,您不能通过 Excel 宏调用来实现。根据MS Doc on Run method,您必须指定全部30 个参数。这是在您的代码示例中完成的。
您可以修改代码以生成动态数量的参数。在这种情况下,我会创建变量 MacroParam1MacroParam2 等到 MacroParam30,初始值为 Missing.Value
在您的代码中,您必须根据某些逻辑设置这些参数。换句话说,对元数据进行编码并将其提取到脚本中。
其实施示例如下。您可以扩展 XML 文件以支持特定的宏,在以下示例中 - ABC

<VBAParameters>
 <Macro Name="ABC" ParamNum="5">
  <Parameters>
    <Parameter Name="SaveToLocation" Value="C:\Temp" />
    <Parameter Name="ConstantName" Value="Post Summary" />
    <Parameter Name="DateFormat" Value="yyyyMM" />
    <Parameter Name="SheetNameInCell" Value="A5" />
    <Parameter Name="AnotherParam" Value="123" />
  </Parameters>
 </Macro>
 <Macro Name="ABC2" ParamNum="4">
  <Parameters>
    <Parameter Name="SaveToLocation" Value="C:\Temp" />
    <Parameter Name="ConstantName" Value="Post Summary" />
    <Parameter Name="DateFormat" Value="yyyyMM" />
    <Parameter Name="SheetNameInCell" Value="A5" />
  </Parameters>
 </Macro>
</VBAParameters>

然后,在您的主代码中,通过将对 doc.GetElementsByTagName("Parameter") 的调用替换为

来获取 XML 元素
XmlNodeList Parameters = doc.XmlNodeList("//Macro[@Name='" 
  + Macro_name + "'/Parameters/Parameter");  

此 Xpath 仅选择名称属性等于 Macro_name 变量的 <Macro> 节点,并获取其参数。然后,您可以按照与原始代码类似的方式处理参数。

按照@Ferdipux 的指示,我已经解决了这个问题,方法是制作一个长度正确的变量数组,默认值为 Missing.value,然后将实际参数插入到开头此数组以提供所需数量的值:

using System;
using System.IO;
using System.Xml;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Text;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.SqlServer.Dts.Runtime;
using System.Reflection;
using VBIDE = Microsoft.Vbe.Interop;


namespace ST_b2148758d9a44ee4bc0d01a2d900ce9d.csproj
{
    [System.AddIn.AddIn("ScriptMain", Version = "1.0", Publisher = "", Description = "")] 

    public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase 
    {
        enum ScriptResults
        {
            Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
            Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
        };

        public void Main()
        {
            Variables UserVariables = null;
            Dts.VariableDispenser.LockForRead("User::VBA_MacroName");
            Dts.VariableDispenser.LockForRead("User::VBA_Script");
            Dts.VariableDispenser.LockForRead("User::VBA_Parameters");
            Dts.VariableDispenser.GetVariables(ref UserVariables);

            //The macro and the macro name were stored in the original data set, so we can get those from local variables.
            string Macro = UserVariables["User::VBA_Script"].Value.ToString();
            string Macro_name = UserVariables["User::VBA_MacroName"].Value.ToString();

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(UserVariables["User::VBA_Parameters"].Value.ToString());
            XmlNodeList Parameters = doc.GetElementsByTagName (@"Parameter");

            object[] AllParamArray = new object[31];
            object[] MyParamArray = new object[Parameters.Count];

            //Fill the array with as many missing values as there are parameters in the Run macro command
            for (int i = 0; i < AllParamArray.Length; i++)
            {
                AllParamArray[i] = Missing.Value;
            }

            //get the parameters that we are actually going to use.
            for (int i = 0; i < Parameters.Count; i++)
            {
                MyParamArray[i] = Parameters[i].Attributes["Value"].Value.ToString();
            }

            //the first parameter is always the macro name
            AllParamArray[0] = Macro_name;

            //after that, we can insert our list of all the parameters we need into the list of all the parameters Excel needs
            MyParamArray.CopyTo(AllParamArray, 1);

            //Get Excel ready to be opened
            Excel.Application ExcelObject = default(Excel.Application);
            Excel.WorkbookClass oBook = default(Excel.WorkbookClass);
            Excel.Workbooks oBooks = default(Excel.Workbooks);
            //get the vba module ready
            VBIDE.VBComponent module = null;

            //open excel in the background
            ExcelObject = new Excel.Application();
            ExcelObject.Visible = false;
            ExcelObject.DisplayAlerts = false;

            //Open our report
            oBooks = ExcelObject.Workbooks;
            oBook = (Excel.WorkbookClass)oBooks.Add(Missing.Value);

            //Add a module to our report and populate it with our vba macro
            module = oBook.VBProject.VBComponents.Add(VBIDE.vbext_ComponentType.vbext_ct_StdModule);
            module.CodeModule.AddFromString(Macro);

            ExcelObject.Run
                (AllParamArray[0], AllParamArray[1], AllParamArray[2], AllParamArray[3], AllParamArray[4], AllParamArray[5], AllParamArray[6], AllParamArray[7], AllParamArray[8],
                 AllParamArray[9], AllParamArray[10], AllParamArray[11], AllParamArray[12], AllParamArray[13], AllParamArray[14], AllParamArray[15], AllParamArray[16], AllParamArray[17],
                 AllParamArray[18], AllParamArray[19], AllParamArray[20], AllParamArray[21], AllParamArray[22], AllParamArray[23], AllParamArray[24], AllParamArray[25], AllParamArray[26],
                 AllParamArray[27], AllParamArray[28], AllParamArray[29], AllParamArray[30]);

            oBook.Close(false,Missing.Value,Missing.Value);

            ExcelObject.Application.Quit();
            ExcelObject =null;
        }
    }
}

这不完全是上一个答案中建议的解决方案,而是基于那里提出的建议以及缺失值不是可选的这一事实。