Dynamically change database type, source etc in Cr

2020-07-30 03:37发布

问题:

I've researched many different ways of how to dynamically change the datasource connection here on StackOverflow. I used and verified almost every c# and vb.net example I could find, but somehow things won't work out as they should.

The idea of our project is to change the datasource connections from the old reports - that are using an xBase dll - to crdb_ado.dll with the VFPOLEDB-provider to connect to Visual Foxpro DBF files (each file representing one table).

I've downloaded the latest Crystal Developer Version for Visual Studio (2012) here: http://scn.sap.com/docs/DOC-35074 To be able to use those components directly in VS without referencing dll's in the Program Files - Business Object directory (as seen in other examples).

I tried verifying the contents via the VS debugger of an older "updated" report (see code below) together with a newly created report in Crystal Reports (latest version, which uses the correct path and settings to connect to the dbf's) so they match up.

However, I encountered these problems:

  1. The code is able to change all parameters and attributes, but saving it discards all changes (file is NOT read only)
  2. Changing table.location throws a com exception (translated: could not load database data, error in report)
  3. Same happens when "attaching" the report document object to the crystal report viewer.
  4. reportDocument.VerifyDatabase() clearly fails
  5. For as far as I can see, all setting are the seem in both files

The dev environment is windows 7/64bit/VS 2012 Pro. Crystal Reports (XI or 2011) is installed on this machine. All our reports were created with version 9 and 11 of Crystal Reports.

Below is one of the adapted code examples that did change all attributes or parameters (except table location). I've also used examples that used Propertybag objects, but those didn't work.

reportDocument1.Load("path to document");

// also tried adding these two lines as a test 
reportDocument1.DataSourceConnections.Clear();
reportDocument1.DataSourceConnections[0].SetConnection(@"c:\testreports","",false);

//changing of table data connections
foreach (Table table in reportDocument1.Database.Tables)
{
    ChangeTableLogonInfo(table);
}

// ---
private void ChangeTableLogonInfo(Table table)
{ 
    // server = place containing *.DBF files
    table.LogOnInfo.ConnectionInfo.ServerName = @"c:\testreports\";
    // set to empty string (looking at generated report in CrRep)
    table.LogOnInfo.ConnectionInfo.DatabaseName = ""; 
    table.LogOnInfo.ConnectionInfo.UserID = "";
    table.LogOnInfo.ConnectionInfo.Password = "";

    // create logon properties
    var connectionAttributes = new DbConnectionAttributes();
    connectionAttributes.Collection.Set("Collating Sequence","Machine");
    connectionAttributes.Collection.Set("Data Source", @"c:\testreports");
    connectionAttributes.Collection.Set("Locale Identifier", 1033);
    connectionAttributes.Collection.Set("OLE DB Services", -5);
    connectionAttributes.Collection.Set("Provider", "VFPOLEDB"); //eg: SQLOLEDB
    connectionAttributes.Collection.Set("Use DSN Default Properties",false);

    // CLEAR and SET NEW attributes for the given table
    table.LogOnInfo.ConnectionInfo.Attributes.Collection.Clear();
    table.LogOnInfo.ConnectionInfo.Attributes.Collection.Add(new NameValuePair2 { Name = "Database DLL", Value = "crdb_ado.dll" });
    table.LogOnInfo.ConnectionInfo.Attributes.Collection.Add(new NameValuePair2 { Name = "QE_DatabaseName", Value = "" });
    table.LogOnInfo.ConnectionInfo.Attributes.Collection.Add(new NameValuePair2 { Name = "QE_DatabaseType", Value = "OLE DB (ADO)" });
    table.LogOnInfo.ConnectionInfo.Attributes.Collection.Add(new NameValuePair2 { Name = "QE_LogonProperties", Value = connectionAttributes });
    table.LogOnInfo.ConnectionInfo.Attributes.Collection.Add(new NameValuePair2 { Name = "QE_ServerDescription", Value = @"c:\testreports" });
    table.LogOnInfo.ConnectionInfo.Attributes.Collection.Add(new NameValuePair2 { Name = "QE_SQLDB", Value = true });
    table.LogOnInfo.ConnectionInfo.Attributes.Collection.Add(new NameValuePair2 { Name = "SSO Enabled", Value = false });

    // gives a COM error
    try
    {
        table.Location = "some-table-name";    
    }
    catch (Exception e)
    {
        // handling
    }   
}

We did notice something "annoying" in Crystal Reports itself. I wonder if it has something to do with it:

When we wanted to change the xbase connection via CR itself, we cannot point it from an "xbase connection dbf" to an "vfpoledb connection dbf" (if you understand my point) without losing all fields in the report.

I tried installing 32 and 64 bit versions of the VS CR developer edition, but it doesn't seem to change anything.

Also, the next piece of code does work in Visual FoxPro (and I am able to read those converted files in my Crystal Reports Document viewer). It's just annoying it cannot be easily done in c# (it seems :) )

lotest = CREATEOBJECT("crystalruntime.application.9")
lorap = lotest.openreport("c:\factuur.rpt")
loData = loRap.Database
LOCAL lnI
lnI = 1
FOR EACH loTable IN lodata.tables
      loconn = loTable.connectionproperties
      loTable.dllname = "crdb_odbc.dll"

      loConn.DeleteAll
      IF lnI = 1
            loCOnn.Add("Database", "Hoofding")
      ELSE 
            loCOnn.Add("Database", "Detail")
      ENDIF 
      loConn.Add("Database Type","ODBC")
      loConn.Add("DSN","DBFACTw")

      lnI = lnI + 1
ENDFOR 

loRap.Saveas("c:\Factuur2.rpt",2048)

Any ideas or suggestions? Thanks

回答1:

I had the same problem and only after a week I managed to solve it.

ApplyLogOnInfo of CrystalDecisions.Shared.Table overwrites ConnectionInfo attributes with the original ones from the report file and updates only username and password.

You have to use PropertyBag instead of DbConnectionAttributes and CrystalDecisions.ReportAppServer.DataDefModel.Table instead of CrystalDecisions.Shared.Table.

Here is my working code:

PropertyBag connectionAttributes = new PropertyBag();
connectionAttributes.Add("Auto Translate", "-1");
connectionAttributes.Add("Connect Timeout", "15");
connectionAttributes.Add("Data Source", Server);
connectionAttributes.Add("General Timeout", "0");
connectionAttributes.Add("Initial Catalog", Database);
connectionAttributes.Add("Integrated Security", false);
connectionAttributes.Add("Locale Identifier", "1040");
connectionAttributes.Add("OLE DB Services", "-5");
connectionAttributes.Add("Provider", "SQLOLEDB");
connectionAttributes.Add("Tag with column collation when possible", "0");
connectionAttributes.Add("Use DSN Default Properties", false);
connectionAttributes.Add("Use Encryption for Data", "0");

PropertyBag attributes = new PropertyBag();
attributes.Add("Database DLL", "crdb_ado.dll");
attributes.Add("QE_DatabaseName", Database);
attributes.Add("QE_DatabaseType", "OLE DB (ADO)");
attributes.Add("QE_LogonProperties", connectionAttributes);
attributes.Add("QE_ServerDescription", Server);
attributes.Add("QESQLDB", true);
attributes.Add("SSO Enabled", false);

CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfo ci = new CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfo();
ci.Attributes = attributes;
ci.Kind = CrConnectionInfoKindEnum.crConnectionInfoKindCRQE;
ci.UserName = Username;
ci.Password = Password;

foreach (CrystalDecisions.ReportAppServer.DataDefModel.Table table in Report.ReportClientDocument.DatabaseController.Database.Tables)
{
    CrystalDecisions.ReportAppServer.DataDefModel.Procedure newTable = new CrystalDecisions.ReportAppServer.DataDefModel.Procedure();

    newTable.ConnectionInfo = ci;
    newTable.Name = table.Name;
    newTable.Alias = table.Alias;
    newTable.QualifiedName = Database + ".dbo." + table.Name;
    Report.ReportClientDocument.DatabaseController.SetTableLocation(table, newTable);
}

foreach (ReportDocument subreport in Report.Subreports)
{
    foreach (CrystalDecisions.ReportAppServer.DataDefModel.Table table in Report.ReportClientDocument.SubreportController.GetSubreportDatabase(subreport.Name).Tables)
    {
        CrystalDecisions.ReportAppServer.DataDefModel.Procedure newTable = new CrystalDecisions.ReportAppServer.DataDefModel.Procedure();

        newTable.ConnectionInfo = ci;
        newTable.Name = table.Name;
        newTable.Alias = table.Alias;
        newTable.QualifiedName = Database + ".dbo." + table.Name;
        Report.ReportClientDocument.SubreportController.SetTableLocation(subreport.Name, table, newTable);
    }
}

I hope this helps also other developers because it's nearly impossible to find this info in SAP documentation or support forums.



回答2:

Neon's answer was a big help to me. Unless the Server was the same, I couldn't get the table loop ApplyLogOnInfo method that most people talk about to work. It would just replace everything with the original values from the report file, like he observed.

In my case, I was just trying to flip between dev/test/prod databases using the connection string in web.config. Using the DatabaseController.ReplaceConnection is what ultimately worked for me. I had to clone (deep copy) the existing ConnectionInfo and make changes to the few relevant properties rather than try to build one up from scratch or modify the existing one directly.

    public static void SetReportLogOnInfo(ReportDocument report)
    {
        if (!report.IsLoaded)
            report.Refresh(); //workaround for report.FileName empty error

        foreach (CrystalDecisions.ReportAppServer.DataDefModel.ConnectionInfo oldInfo in report.ReportClientDocument.DatabaseController.GetConnectionInfos())
        {
            var newInfo = oldInfo.Clone(true);
            newInfo.UserName = [ConnectionString.UserID];
            newInfo.Password = [ConnectionString.Password];
            newInfo.Attributes["QE_LogonProperties"]["Server"] = [ConnectionString.DataSource];
            report.ReportClientDocument.DatabaseController.ReplaceConnection(oldInfo, newInfo, null, CrDBOptionsEnum.crDBOptionDoNotVerifyDB);
        }
    }