C# – Crystal Reports for Visual Studio throws Missing Parameter Values exception with subreport links

ccrystal-reportsvisual studio 2010

I made a code in C# to be used with Crystal Reports for Visual Studio, version 13.0.5.891, and Visual Studio 2010. It basically receives the name of a report file and a Dictionary with the parameters to pass to the .rpt file and shows the generated report on browser window. It's part of an ASP.NET MVC 3 application.

Here's the code:

    /// <summary>
    /// Shows the report
    /// </summary>
    /// <param name="reportName">Report name</param>
    /// <param name="parameters">Dictionary containing the pairs "key/value" of each parameter.</param>
    public static void ShowReport(string reportName, Dictionary<string, object> parameters)
    {
        if (!string.IsNullOrEmpty(reportName))
        {
            ReportDocument rd = new ReportDocument();
            CrystalDecisions.Shared.ConnectionInfo connection = new CrystalDecisions.Shared.ConnectionInfo();
            TableLogOnInfo tableLogin = new TableLogOnInfo();

            reportName += "_o.rpt"; // all .rpt files we use here have names ending with "_o"

            //Find and load the report.
            string strRptPath = Path.Combine(System.Web.HttpContext.Current.Server.MapPath("~/"), "Reports", reportName);
            rd.Load(strRptPath);

            //Get the application connection string to build de connection info object
            OracleConnectionStringBuilder builder = new OracleConnectionStringBuilder(ConnectionString.providerConnectionString());

            CrystalDecisions.ReportAppServer.DataDefModel.Table boTable =
                new CrystalDecisions.ReportAppServer.DataDefModel.Table();

            //boMainPropertyBag: These hold the attributes of the tables ConnectionInfo object
            PropertyBag boMainPropertyBag = new PropertyBag();
            //boInnerPropertyBag: These hold the attributes for the QE_LogonProperties
            //In the main property bag (boMainPropertyBag)
            PropertyBag boInnerPropertyBag = new PropertyBag();

            //Set the attributes for the boInnerPropertyBag
            boInnerPropertyBag.Add("Server", builder.DataSource);
            boInnerPropertyBag.Add("Trusted_Connection", "False");

            //Set the attributes for the boMainPropertyBag
            boMainPropertyBag.Add("Database DLL", "crdb_oracle.dll");
            boMainPropertyBag.Add("QE_DatabaseName", "");
            boMainPropertyBag.Add("QE_DatabaseType", "Oracle Server");
            //Add the QE_LogonProperties we set in the boInnerPropertyBag Object
            boMainPropertyBag.Add("QE_LogonProperties", boInnerPropertyBag);
            boMainPropertyBag.Add("QE_ServerDescription", "XE");
            boMainPropertyBag.Add("QE_SQLDB", "False");
            boMainPropertyBag.Add("SSO Enabled", "False");

            //Create a new ConnectionInfo object
            CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfo boConnectionInfo =
            new CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfo();
            //Pass the database properties to a connection info object
            boConnectionInfo.Attributes = boMainPropertyBag;
            //Set the connection kind
            boConnectionInfo.Kind = CrConnectionInfoKindEnum.crConnectionInfoKindCRQE;
            //**EDIT** Set the User Name and Password if required.
            boConnectionInfo.UserName = builder.UserID;
            boConnectionInfo.Password = builder.Password;
            //Pass the connection information to the table
            boTable.ConnectionInfo = boConnectionInfo;

            //Get the Database Tables Collection for your report
            CrystalDecisions.ReportAppServer.DataDefModel.Tables boTables;
            boTables = rd.ReportClientDocument.DatabaseController.Database.Tables;

            //For each table in the report:
            // - Set the Table Name properties.
            // - Set the table location in the report to use the new modified table
            int numtable = 0;
            foreach (CrystalDecisions.ReportAppServer.DataDefModel.Table table in boTables)
            {
                boTable.Name = table.Name;
                boTable.QualifiedName = builder.UserID + "." + table.Name;
                boTable.Alias = table.Name;

                rd.ReportClientDocument.DatabaseController.SetTableLocation(boTables[numtable], boTable);
                numtable++;
            }

            //Code for subreports.
            CrystalDecisions.ReportAppServer.Controllers.DatabaseController boDatabaseCtl = null;
            CrystalDecisions.ReportAppServer.DataDefModel.Database boDatabase = null;
            CrystalDecisions.ReportAppServer.ClientDoc.ISCDReportClientDocument boClientDoc = rd.ReportClientDocument;
            Strings subreportNames=boClientDoc.SubreportController.GetSubreportNames();
            for (int i = 0; i < subreportNames.Count; i++)
            {
                boDatabaseCtl = boClientDoc.SubreportController.GetSubreport(subreportNames[i]).DatabaseController;
                boDatabase = boDatabaseCtl.Database;
                boTables = boDatabase.Tables;

                numtable = 0;
                foreach (CrystalDecisions.ReportAppServer.DataDefModel.Table t in boTables)
                {
                    boTable.Name = t.Name;
                    boTable.QualifiedName = builder.UserID + "." + t.Name;
                    boTable.Alias = t.Name;

                    boDatabaseCtl.SetTableLocation(boTables[numtable], boTable);
                    numtable++;
                }
            }

            //Verify the database after adding substituting the new table.
            //To ensure that the table updates properly when adding Command tables or Stored Procedures.
            rd.VerifyDatabase();

            //Set the parameters.
            foreach (string key in parameters.Keys)
            {
                object value;
                if (parameters.TryGetValue(key, out value))
                    rd.SetParameterValue(key, value);
            }

            //Export(show) the report.
            rd.ExportToHttpResponse(ExportFormatType.PortableDocFormat, System.Web.HttpContext.Current.Response, false, "crReport");
            rd.Close();
            rd.Dispose();
        }
        else
        {
            // throws an exception saying the .rpt name was not informed
            throw new Exception(Idiomas.Atual.NomeRelatórioNãoInformado);   
        }
    }

It works fine when I use a report without subreports, or a report that has subreports which receive parameters from outside, but when I use subreports with subreport links it throws an exception with the message: Missing Parameter Values.

Any idea on what I'm missing here? Any help is greatly appreciated. Thanks!

Best Answer

My situation differs from you in the higher level particulars, but I've been programmatically assigning parameters too. I haven't been using the SetParameterValue method, though.

Instead, I loop through the parameters collection in the data definition:

int intNumParameters = reportDoc.ParameterFields.Count;

for(int i = 0; i < intNumParameters; i++)
{
    if(!reportDoc.DataDefinition.ParameterFields[i].IsLinked())
    {
        ParameterValues pVals = new ParameterValues();
        ParameterDiscreteValue pdv = new ParameterDiscreteValue();
        pdv.Value = objValue;
        pVals.Add(pdv);
        reportDoc.DataDefinition.ParameterFields[i].ApplyCurrentValues(pVals);
    }
}

reportDoc.Export(expOpts);

I imagine you could probably do a lot of that inline, but this is just part of a little tool I whipped up for my own use. I probably should get the length of the DataDefinition.ParameterFields array instead of the ReportDocument.ParameterFields array, but it works regardless.

I don't know whether this will help you as I'm intentionally skipping over the linked parameters which appears to be at the root of your problem. But I can, at least, offer another way of assigning parameters. I will note that I too have subreports in the majority of my reports and haven't noticed any difference between processing those with them and those without.

Hope this helps :)