问题
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:
- The code is able to change all parameters and attributes, but saving it discards all changes (file is NOT read only)
- Changing table.location throws a com exception (translated: could not load database data, error in report)
- Same happens when "attaching" the report document object to the crystal report viewer.
- reportDocument.VerifyDatabase() clearly fails
- 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);
}
}
来源:https://stackoverflow.com/questions/15161227/dynamically-change-database-type-source-etc-in-crystal-reports-for-visual-stud