Переменное количество параметров C# Interop

Существует пакет dtsx, который запускает отчеты SSRS с использованием таблицы конфигурации, а затем выполняет макрос, где это необходимо, в созданном файле .xls (это сервер 2008 R2).

Каждая строка таблицы конфигураций содержит сведения об отчете, который необходимо запустить, сведения о макросе и список параметров и значений в формате xml, которые необходимо передать макросу; в пакете есть цикл foreach, который запускает отчет, добавляет макрос в выходной файл, запускает макрос и движется дальше.

Создается впечатление, что он был максимально обобщен, чтобы в таблицу конфигураций можно было вписать любой отчет и любой макрос.

Это работает нормально, но при всем том, что он кажется гибким, фактический C # в коде скрипта, который загружает и запускает макрос, имеет очень жесткую структуру, а это означает, что можно запускать только макросы с ровно 4 параметрами - я хотел бы сделать его более гибким, но я изо всех сил пытаюсь понять, как дать команде ExcelObject.Run() правильное (переменное) количество параметров в зависимости от количества узлов в конфигурации xml.

У меня нет большого опыта работы с C#, но, кажется, я прочитал эту статью Microsoft и эту статью Stackoverflow, которую я могу создайте массив параметров и передайте это... Я просто не могу понять, как это сделать. Вот существующий скрипт:

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 возвращает параметры в предсказуемом порядке, поэтому имена не нужны.


person High Plains Grifter    schedule 28.03.2018    source источник


Ответы (2)


На ваш вопрос

после прочтения этой статьи Microsoft и этой статьи Stackoverflow кажется, что я могу создать массив параметров и передать это...

Нет, вы не можете сделать это с помощью вызова макроса Excel. В соответствии с метод MS Doc on Run необходимо указать все 30 параметров. Это делается в вашем образце кода.
Вы можете изменить код, чтобы количество параметров было динамическим. В этом случае я бы создал переменные MacroParam1, MacroParam2 и т. д. для 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>

Затем в основном коде выберите элементы XML, заменив вызов doc.GetElementsByTagName("Parameter") на

XmlNodeList Parameters = doc.XmlNodeList("//Macro[@Name='" 
  + Macro_name + "'/Parameters/Parameter");  

Этот Xpath выбирает только узел <Macro> с атрибутом Name, равным переменной Macro_name, и получает его параметры. Затем вы обрабатываете параметры аналогично исходному коду.

person Ferdipux    schedule 29.03.2018

Следуя указателю @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;
        }
    }
}

Это не совсем то решение, которое было предложено в предыдущем ответе, но оно основано на сделанных там предложениях и на том факте, что отсутствующие значения не являются необязательными.

person High Plains Grifter    schedule 05.04.2018