Changing the PivotCache connection of an Excel spreadsheet in C# - c#

I'm loading an excel spreadsheet with pivot tables and charts in it into a web-browser control using C#. The spreadsheets have several connection strings all pointing to a development database. I would like to be able to change the connection strings to one that has been supplied by the user when my application is run.
I have already managed to get this to work when the spreadsheet was created programmatically and the SourceType of the pivot caches are set to 'External'. However, when a spreadsheet that was created in Excel is loaded the source type is set to 'Database' and exceptions are thrown when the 'Connection' property is accessed.
Is there a way to change the SourceType (read-only) property or the connection string of such a spread sheet?
Here is a sample of my code which is based on a solution to a similar problem.
EXCEL.Worksheet sheet = (EXCEL.Worksheet)_application.ActiveSheet;
foreach (EXCEL.PivotTable table in sheet.PivotTables())
{
table.PivotCache().Connection = ConnectionString;
table.RefreshTable();
}
I have also tried this
var workBooks = _application.Workbooks.Cast<EXCEL.Workbook>();
var pivotCaches = workBooks.SelectMany(arg => GetPivotCaches(arg));
foreach (EXCEL.PivotCache cache in pivotCaches)
{
cache.Connection = ConnectionString;
}
In both cases I get a System.Runtime.InteropServices.COMException as soon as I access the Connection property of the pivot cache. Any ideas?

Here is the fix for the problem based on a solution which I found here.
//update the connections
foreach (EXCEL.WorkbookConnection connection in workbook.Connections)
{
if (connection.Type.ToString() == "xlConnectionTypeODBC")
{
connection.ODBCConnection.BackgroundQuery = false;
connection.ODBCConnection.Connection = ConnectionString;
}
else
{
connection.OLEDBConnection.BackgroundQuery = false;
connection.OLEDBConnection.Connection = ConnectionString;
}
}
//Refresh all data
workbook.RefreshAll();
This fixed the problem with most of the reports throwing exceptions. The only one that didn't work was re-created from scratch (it was really old and had been around the block!)
Hope this helps others.

Related

How can I get the the Microsoft Access Database Application Title via C#

I need to get the Application Title from an access database via C#. I have seen some samples using Office VBA: I.E: https://learn.microsoft.com/en-us/office/vba/api/access.application.apptitle
The issue is that I don't have a reference to a class library to be able to access the Application.AppTitle property. I have tried a few references, most notably:
using Microsoft.Office.Interop.Access.Dao
But I can't access the Application.AppTitle via any of the properties. For example:
var dbe = new Microsoft.Office.Interop.Access.Dao.DBEngine();
var db = dbe.OpenDatabase(#"c:\\Sample.mdb");
// Show database properties
// db.Properties. "When I expand this there is no AppTitle"
Does anyone have any other approach that has worked for them to access the MS Access AppTitle via C#?
Thanks in advance!
Ian
The AppTitle property doesn't show up until you set it in Access or via code (and with RefreshTitleBar
Your code works because you're looping to check for the name. You won't find the property if it's empty.
You can use the loop method like you do above or check for it directly using database.properties("AppTitle") - just make sure you trap for an error in case it's empty
Update: I figured it out. This was different than I expected, but the solution was:
var dbEngine = new DBEngine();
var database = dbEngine.OpenDatabase(#"c:\\Sample.mdb");
Microsoft.Office.Interop.Access.Dao.Property allowBypassKeyProperty = null;
foreach (Microsoft.Office.Interop.Access.Dao.Property property in database.Properties)
{
if (property != null)
{
if (property.Name == "AppTitle")
{
MessageBox.Show(property.Name.ToString());
MessageBox.Show(property.Value.ToString());
}
}
}

SQL Server Reporting Services Cannot Set Data Source Connection String

We have a number of SSRS sites serving reports to various locations. Each of these servers all have custom connections in each and every report (don't ask why, that's a tale too torrid to tell). Our goal is to replace all of these custom data sources with a single shared data source for all reports on each server.
To that end, I have created a C# program that will find each report on each server and point the current custom data sources to a currently existing shared data source. This executes and seems to work fine.
My next goal is to use C# to create the shared data source on each server where none currently exists.
My current dilemma arises here:
private static void CreateSharedDataSource(string user, string password, string connection)
{
DataSourceDefinition source = new DataSourceDefinition();
source.CredentialRetrieval = CredentialRetrievalEnum.Store;
source.ConnectString = connection;
source.Enabled = true;
source.EnabledSpecified = true;
source.Extension = "SQL";
source.ImpersonateUser = false;
source.ImpersonateUserSpecified = false;
source.Prompt = "Enter a user name and password to access the data source:";
source.UserName = user;
source.Password = password;
source.WindowsCredentials = true;
source.OriginalConnectStringExpressionBased = true;
source.UseOriginalConnectString = true;
try
{
service.CreateDataSource("DbDataSource", "/DsFolder", false, source, null);
Console.WriteLine("Data source created successfully");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
The data source is created correctly and the user name and password are updated correctly. The problem is that, when I look at the newly created data source on the server, the Connect string property is empty and, yes, the correct value is being passed to it in the method above. If I plug that value into the shared source on the server and test the connection, it works fine, but I cannot get the C# program to update that value itself.
So, is there something subtle I'm missing? Am I misinterpreting a setting up there? Did I set a value wrong?
Clues appreciated.
I've never tried anything like this but a quick bit of research uncovered this which may be helpful.
https://learn.microsoft.com/en-us/dotnet/api/reportservice2010.datasourcedefinition.originalconnectstringexpressionbased?view=sqlserver-2016
It states
"Expression-based connection strings are not supported in shared data
sources."
Assuming the conneciton string is just a plain text string then I would guess that you could set this to false. This may help preserve the string you pass in.
Alright, found my answer. I had a pre-existing data source that was working so, instead of creating it from scratch, I copied that and only changed the name. That created a data source where the Connect string did persist. Comparing the settings in that with what I was setting revealed:
source.UseOriginalConnectString = false;
whereas, my code was:
source.UseOriginalConnectString = true;
Looking that up in docs and it tells me "If true, the value of the ConnectString property is ignored."
Hmmm... that's intuitive. That's not what that sounds like at all. :)

Set Read-Only Access for Crystal Report in AlwaysOn environment

I have two SQL servers that are being load balanced - AlwaysOn. Only the second one of these servers is supposed to be used for crystal reports. I would like to access the second SQL server using the readOnly flag in the connection string: ApplicationIntent=ReadOnly
In my C# class I am running the crystal reports based on ConnectionInfo()
var myConnectionInfo = new ConnectionInfo();
Tables myTables = reportDocument.Database.Tables;
for (int i = 0; i < myTables.Count; i++)
{
var myTable = myTables[i];
var myTableLogonInfo = myTable.LogOnInfo;
myConnectionInfo.ServerName = 'serverName';
myConnectionInfo.DatabaseName = 'databaseName';
myConnectionInfo.UserID = 'userId';
myConnectionInfo.Password = 'password';
myTableLogonInfo.ConnectionInfo = myConnectionInfo;
myTable.ApplyLogOnInfo(myTableLogonInfo);
}
I haven't found a way to set ApplicationIntent=ReadOnly though. Is this supposed to be done setting myConnectionInfo.Attributes? Unfortunately I haven't found an answer on this yet but unanswered questions:
https://archive.sap.com/discussions/thread/3861287
https://archive.sap.com/discussions/thread/3791155
Unfortunately I did not find a way to use the flag ApplicationIntent=ReadOnly in my posted code snippet.
What I ended up doing:
Instead of using the load balancer IP address (or host name), I am using the IP address of the reporting server directly.
I couldn't find any written documentation whether or not one can use ApplicationIntent=ReadOnly.
Rather than using ConnectionInfo, you could use System.Data.SqlClient.SqlConnectionStringBuilder, which has a settable ApplicationIntent property.

dynamically create link tables and rename them in access 2007/2003 with c#

I'm looking to be able to dynamically create linked tables with C# in an accdb/mdb existing file. Is this possible? The idea would be for every linked table ALREADY in a given access database dynamically create a new linked table and then the second part of the problem would be to then rename name this newly created table to the pre existing table.
If its not already clear there is a migration going on from one database to another so every pre existing table has an equivalent table in the new database but they need to have the same name in the Access database in order for the queries to work etc.
Is this even possible?
EDIT:
I have created a test database that contains one linked table to an ODBC database. I have also created a simple query that just counts the rows. My C# code runs the query first and then attempts to change the connection string with the code:
var dbe = new DBEngine();
Database db = dbe.OpenDatabase(#"C:\Users\x339\Documents\Test.accdb");
foreach (TableDef tbd in db.TableDefs)
{
if (tbd.Connect.Length > 5)
{
if (tbd.Connect.Substring(0, 5).Equals("ODBC;"))
{
tbd.Connect = tbd.Connect.Replace("ODBC;DSN=ILACFEUC;UID=cloaseuc;DBQ=ILACFEUC;DBQ=W;APA=T;EXC=F;FEN=T;QTO=F;FRC=10;FDL=10;LOB=T;RST=T;BTD=F;BNF=F;BAM=IfAllSuccessful;NUM=NLS;DPM=F;MTS=F;MDI=F;CSR=F;FWC=F;FBS=64000;TLO=0;MLD=0;ODA=F;;TABLE=CLOASEUCDBA.T_BASIC_POLICY", "ODBC;DSN=ILACFEUC;UID=cloaseuc;DBQ=ILACFEUC;DBQ=W;APA=T;EXC=F;FEN=T;QTO=F;FRC=10;FDL=10;LOB=T;RST=T;BTD=F;BNF=F;BAM=IfAllSuccessful;NUM=NLS;DPM=F;MTS=F;MDI=F;CSR=F;FWC=F;FBS=64000;TLO=0;MLD=0;ODA=F;;TABLE=CLOASEUCDBA.T_BILLING_INFORMATION");
tbd.RefreshLink();
}
}
}
however it is not working. If I open the database up in access the connection string is unchanged?
It sounds like you really just want to change the external database to which the existing linked tables are connected. In that case you could do it in C# like this:
// This code requires the following COM reference in your project:
//
// Microsoft Office 14.0 Access Database Engine Object Library
//
// and the declaration
//
// using Microsoft.Office.Interop.Access.Dao;
//
// at the top of the class file
var dbe = new DBEngine();
Database db = dbe.OpenDatabase(#"C:\Users\Public\FrontEnd.accdb");
foreach (TableDef tbd in db.TableDefs)
{
if (tbd.Connect.Length > 10)
{
if (tbd.Connect.Substring(0, 10).Equals(";DATABASE="))
{
tbd.Connect = tbd.Connect.Replace("oldBackEnd.accdb", "newBackEnd.accdb");
tbd.RefreshLink();
}
}
}
db.Close();

Unloading COM Objects C# [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to properly clean up Excel interop objects in C#
I am using EXCEL INTEROP for reading excel files in my .NET application. However I see that after am done with it, I still see the EXCEL.EXE in Windows Task Manager.
The code is as follows:
ApplicationClass excel = new ApplicationClass();
Workbooks workBooks = excel.Workbooks;
Workbook workBook = workBooks.Open(fileName,0,true,5,"","",true,XLPlatform.xlWindows,"\t",false,false,0,true,1,0);
foreach (Name name in workBook.Names)
{
try
{
// =#REF!#REF! indicates that the named range refers to nothing. Ignore these..
if (name.Value != "=#REF!#REF!")
{
if (!retNamedRanges.ContainsKey(name.Name))
{
string keyName = name.Name;
object value = name.RefersToRange.get_Value(missing);
retNamedRanges.Add(keyName, value);
}
}
}
catch (Exception ex)
{
}
}
if(workBook!=null)
{
workBook.Close(false,missing,missing);
}
if(workBook!=null)
{
workBooks.Close();
}
System.Runtime.InteropServices.Marshal.ReleaseComObject(workBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(workBooks);
workBook = null;
workBooks = null;
excel.Application.Quit();
excel.Quit();
excel = null;
I have tried to do all possible things to clean up, but still it does not go. There are multiple EXCEL files that I need to read. Typically after my application executes I see multiple instances of EXCEL.EXE.
Is there anything else am missing with the clean up?
Many thanks in advance
"Some process specific to my application I am doing..."
Actually, this is most likely where the problem lies. As in the referenced duplicate, if you reference a property of the ApplicationClass then you'll need to make sure you dereference that property before the garbage collector will tidy up and remove Excel.
So, for instance, copy any data you need to string, int, etc. (or your own internal types based on these base types).
Try using Marshal.*Final*ReleaseComObject instead of ReleaseComObject.
Also call it on your "ApplicationClass excel" instance.

Categories