I am creating an Excel workbook in code behind and save it as both XLSX and PDF. I use a template workbook for this that has formatting and formulae to be evaluated after the generating is done. When I open the Excel file, the formulae evaluate only when I set ForceFormulaRecalculation to true. When I do the same with the PDF file, I get #VALUE! where the results should be. My relevant code:
ReportGenerator generator = new ReportGenerator();
List<Activity> activities = GetActivitiesForItemCollection(items);
generator.CreateWorkbook(templatePath);
generator.Year = int.Parse(year);
generator.Month = int.Parse(month);
activities = generator.SortActivitiesByDateTime(activities);
activities = generator.GenerateBreaksForProject(activities);
bool isExternalReport = false;
if (project == "Intern")
isExternalReport = true;
generator.GenerateReports(activities, isExternalReport);
if (pdf && !xlsx)
generator.SaveReportToList(OutputFileType.PDF, generator.AssembleFileName(UserProperties.FullName, project, month, year, OutputFileType.PDF));
else if (xlsx && !pdf)
generator.SaveReportToList(OutputFileType.XLSX, generator.AssembleFileName(UserProperties.FullName, project, month, year, OutputFileType.XLSX));
else
{
generator.SaveReportToList(OutputFileType.XLSX, generator.AssembleFileName(UserProperties.FullName, project, month, year, OutputFileType.XLSX));
generator.SaveReportToList(OutputFileType.PDF, generator.AssembleFileName(UserProperties.FullName, project, month, year, OutputFileType.PDF));
}
Here is my code where I do the evaluation:
public void SaveReportToList(OutputFileType outputType, string filename)
{
string siteUrl = SPContext.Current.Web.Url;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (MemoryStream mStream = new MemoryStream())
{
sheet.PrintSetup.Landscape = true;
sheet.PrintSetup.FitWidth = 1;
sheet.PrintSetup.FitHeight = 1;
XSSFFormulaEvaluator.EvaluateAllFormulaCells(workbook);
workbook.Write(mStream);
using (MemoryStream crutchStream = new MemoryStream(mStream.ToArray()))
{
using (SPSite spsite = new SPSite(siteUrl))
{
using (SPWeb spweb = spsite.OpenWeb())
{
....
I have also tried it this way:
private void ForceCalculateSheet()
{
XSSFFormulaEvaluator helper = (XSSFFormulaEvaluator)workbook.GetCreationHelper().CreateFormulaEvaluator();
for(int i = 0; i<sheet.LastRowNum; i++)
{
XSSFRow row = (XSSFRow)sheet.GetRow(i);
for(int j = 0; j< row.LastCellNum; j++)
{
XSSFCell cell = (XSSFCell)row.GetCell(j);
if(cell != null && cell.CellType == CellType.Formula)
{
helper.EvaluateFormulaCell(cell);
}
}
}
}
Here are a couple of formulae I try to evaluate (some of the cell values are 24-hour time values):
=IF(D37-C37-E37>0;D37-C37-E37;0)
=INT(J39/60)
=J39-C39*60
The weird thing is, when I trace the evaluation in Excel, it goes through until the last step. Then suddenly the value becomes #VALUE!.
Anybody got an idea what's going on here?
Just to be careful, I updated NPOI to it's latest .NET Version (2.1.3.1). This still didn't fix the problem.
When I click a cell that's in the formula, change its value, then confirm it with pressing Enter, the formula evaluates correctly. Meaning there shouldn't be anything wrong with the formulae themselves.
I solved it by manually doing the calculations. This isn't my long-term solution as users should be able to insert their own calculations, but it's the only solution I can come up with right now besides using another Excel framework (bad planning ;)).
Related
I am working with Csv file and datagridview in a C# project for a inventory app, I try to update a row to CSV file!
i need to update if user edit a row current word with a new word but my problem here is i need save the current word and new word and get total in pseudo code example:
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if(row in column is modified)
update specific row with comma to current file and load it...
}
Csv file is look like,
Current:
1;2;;4;5
Update:
1;2,A;;4;5 changed device A total: 1 time...
Next row modified :
1;A;;4,B,C;5 changed device B and C total change : 2 time...
With a database it's easy to update data but i don't have sql server installed so this option has not for me i think..
My goal is for tracking device out/in so if you have a solution please share it.
Short of using an SQL server, maybe something like this could help? LiteDB You'd have your LiteDB to host your data, and export it CSV whenever you need. Working with CSV files usually means you'll re-write the whole file every time there is an update to make... Which is slow and cumbersome. I recommend you use CSV to transport data from Point A to Point B, but not to maintain data.
Also, if you really want to stick to CSV, have a look at the Microsoft Ace OLEDB driver, previously known as JET driver. I use it to query CSV files, but I have never used it to update... so your mileage may vary.
Short of using an actual DataBase or a database driver, you'll have to use a StreamReader along with a StreamWriter. Read the file with the StreamReader, write the new file with the StreamWriter. In your StreanReader. This implies you'll have code in your StreamReader to find the correct Line(s) to update.
Here's the class I created and am using to interact with LiteDB. It's not all that robust, but it did exactly what I needed it to do at the time. I had to make changes to a slew of products hosted on my platform, and I used this to keep track of the progress.
using System;
using LiteDB;
namespace FixProductsProperty
{
public enum ListAction
{
Add = 0,
Remove,
Update,
Disable,
Enable
}
class DbInteractions
{
public static readonly string dbFilename = "MyDatabaseName.db";
public static readonly string dbItemsTableName = "MyTableName";
public void ToDataBase(ListAction incomingAction, TrackingDbEntry dbEntry = null)
{
if (dbEntry == null)
{
Exception ex = new Exception("dbEntry can not be null");
throw ex;
}
// Open database (or create if not exits)
using (var db = new LiteDatabase(dbFilename))
{
var backupListInDB = db.GetCollection<TrackingDbEntry>(dbItemsTableName);
//ovverride action if needed
if (incomingAction == ListAction.Add)
{
var tempone = backupListInDB.FindOne(p => p.ProductID == dbEntry.ProductID);
if (backupListInDB.FindOne(p => p.ProductID == dbEntry.ProductID) != null)
{
//the record already exists
incomingAction = ListAction.Update;
//IOException ex = new IOException("Err: Duplicate. " + dbEntry.ProductID + " is already in the database.");
//throw ex;
}
else
{
//the record does not already exist
incomingAction = ListAction.Add;
}
}
switch (incomingAction)
{
case ListAction.Add:
backupListInDB.Insert(dbEntry);
break;
case ListAction.Remove:
//backupListInDB.Delete(p => p.FileOrFolderPath == backupItem.FileOrFolderPath);
if (dbEntry.ProductID != 0)
{
backupListInDB.Delete(dbEntry.ProductID);
}
break;
case ListAction.Update:
if (dbEntry.ProductID != 0)
{
backupListInDB.Update(dbEntry.ProductID, dbEntry);
}
break;
case ListAction.Disable:
break;
case ListAction.Enable:
break;
default:
break;
}
backupListInDB.EnsureIndex(p => p.ProductID);
// Use Linq to query documents
//var results = backupListInDB.Find(x => x.Name.StartsWith("Jo"));
}
}
}
}
I use it like this:
DbInteractions yeah = new DbInteractions();
yeah.ToDataBase(ListAction.Add, new TrackingDbEntry { ProductID = dataBoundItem.ProductID, StoreID = dataBoundItem.StoreID, ChangeStatus = true });
Sorry... my variable naming convention sometimes blows...
I know this question exists, because it's mine and I put up 500 bounty points on it:
Exporting C# report to Excel when there are more than 5K lines
The answer got me over the hump (to some degree) but we're sort of at the point where we just accept that abnormally large datasets just can't be exported via our ASP front end, so we ship those requests off to our SQL Server DBs, who then run the appropriate stored procedures and copy/paste to Excel spreadsheets.
My question here is; can someone definitively answer whether or not it's absolutely impossible to export a large dataset to an Excel spreadsheet via a ASP front end? Once a particular report hits about 8K records or something, it just can't seem to be done. I'm just trying to determine whether any other potential tweak can be made, or if that much data is just more than ASP can handle?
Well... since I've streamed gigabytes of data directly from ASP.NET, I'm pretty sure you're doing something wrong. Try to isolate the problem first - is it in putting the data into the session, is it request / response limits, is it request timeouts? Figure out where the problem is, and then go ahead and solve it! :)
In general terms, there's no reason why you should put the data in a DataSet first. Instead, use a SqlDataReader and write the data to output in chunks. This way you'll avoid having the whole data set in memory; the same way, you can just directly write to the output stream, without buffering the generated HTML in memory. Why do you keep data in Session? Wouldn't it be better to just hold the parameters necessary to retrieve it from the DB as needed, using the DataReader?
If you're having trouble with timeouts, periodical Flushes help. This also helps reduce the memory footprint on the ASP.NET side.
Saving the output data to a file on the server first also helps, and it allows you to wire up partial file downloads too - just make sure you actually have enough space on the drive.
EDIT:
Ok, so you've got an SqlCommand. Instead of using it in a SqlDataAdapter, you can do something like this (cmd being your SqlCommand instance):
HtmlTextWriter wr = new HtmlTextWriter(Response.Output);
using (var rdr = cmd.ExecuteReader())
{
int index = 0;
wr.WriteBeginTag("table");
wr.WriteLine("<tr><td>Column 1</td><td>Column 2</td></tr>");
while (rdr.Read())
{
wr.WriteBeginTag("tr");
wr.WriteBeginTag("td");
wr.Write(rdr["Column1"]);
wr.WriteEndTag("td");
wr.WriteBeginTag("td");
wr.Write(rdr["Column2"]);
wr.WriteEndTag("td");
wr.WriteEndTag("tr");
if (index++ % 1000 == 0) Response.Flush();
}
wr.WriteEndTag("table");
}
I have not tested it, so it might need some tweaking, but the idea should be pretty obvious.
It is possible to do this as I have actually just finished some code specifically to do this as part of a reporting project that I am working on where we have in-excess of 20K records that need to be pulled back and exported into excel.
I will pull out the code and stick it on github for you to look at.
I am actually using NPOI's excel processing package and then using my custom code I am able to process any List of classes dynamically into a dataset and then dump it into the worksheets.
I need to tidy up the code for you but I should have something ready for you this evening.
This code will work for both desktop and web apps.
To give you an idea my code has been able to process a dataset of over 30K relatively quickly. I have to resolve an issue with datasets over the limit of 65536 records first before it is ready for you.
The nice thing with this solution means it doesn't rely on excel being installed on the machine hosting the solution.
EDIT
I have loaded a project onto github here:
https://github.com/JellyMaster/ExcelHelper
but here is the main bit that does all the excel processing:
public static MemoryStream CreateExcelSheet(DataSet dataToProcess)
{
MemoryStream stream = new MemoryStream();
if (dataToProcess != null)
{
var excelworkbook = new HSSFWorkbook();
foreach (DataTable table in dataToProcess.Tables)
{
var worksheet = excelworkbook.CreateSheet();
var headerRow = worksheet.CreateRow(0);
foreach (DataColumn column in table.Columns)
{
headerRow.CreateCell(table.Columns.IndexOf(column)).SetCellValue(column.ColumnName);
}
//freeze top panel.
worksheet.CreateFreezePane(0, 1, 0, 1);
int rowNumber = 1;
foreach (DataRow row in table.Rows)
{
var sheetRow = worksheet.CreateRow(rowNumber++);
foreach (DataColumn column in table.Columns)
{
sheetRow.CreateCell(table.Columns.IndexOf(column)).SetCellValue(row[column].ToString());
}
}
}
excelworkbook.Write(stream);
}
return stream;
}
public static DataSet CreateDataSetFromExcel(Stream streamToProcess, string fileExtentison = "xlsx")
{
DataSet model = new DataSet();
if (streamToProcess != null)
{
if (fileExtentison == "xlsx")
{
XSSFWorkbook workbook = new XSSFWorkbook(streamToProcess);
model = ProcessXLSX(workbook);
}
else
{
HSSFWorkbook workbook = new HSSFWorkbook(streamToProcess);
model = ProcessXLSX(workbook);
}
}
return model;
}
private static DataSet ProcessXLSX(HSSFWorkbook workbook)
{
DataSet model = new DataSet();
for (int index = 0; index < workbook.NumberOfSheets; index++)
{
ISheet sheet = workbook.GetSheetAt(0);
if (sheet != null)
{
DataTable table = GenerateTableData(sheet);
model.Tables.Add(table);
}
}
return model;
}
private static DataTable GenerateTableData(ISheet sheet)
{
DataTable table = new DataTable(sheet.SheetName);
for (int rowIndex = 0; rowIndex <= sheet.LastRowNum; rowIndex++)
{
//we will assume the first row are the column names
IRow row = sheet.GetRow(rowIndex);
//a completely empty row of data so break out of the process.
if (row == null)
{
break;
}
if (rowIndex == 0)
{
for (int cellIndex = 0; cellIndex < row.LastCellNum; cellIndex++)
{
string value = row.GetCell(cellIndex).ToString();
if (string.IsNullOrEmpty(value))
{
break;
}
else
{
table.Columns.Add(new DataColumn(value));
}
}
}
else
{
//get the data and add to the collection
//now we know the number of columns to iterate through lets get the data and fill up the table.
DataRow datarow = table.NewRow();
object[] objectArray = new object[table.Columns.Count];
for (int columnIndex = 0; columnIndex < table.Columns.Count; columnIndex++)
{
try
{
ICell cell = row.GetCell(columnIndex);
if (cell != null)
{
objectArray[columnIndex] = cell.ToString();
}
else
{
objectArray[columnIndex] = string.Empty;
}
}
catch (Exception error)
{
Debug.WriteLine(error.Message);
Debug.WriteLine("Column Index" + columnIndex);
Debug.WriteLine("Row Index" + row.RowNum);
}
}
datarow.ItemArray = objectArray;
table.Rows.Add(datarow);
}
}
return table;
}
private static DataSet ProcessXLSX(XSSFWorkbook workbook)
{
DataSet model = new DataSet();
for (int index = 0; index < workbook.NumberOfSheets; index++)
{
ISheet sheet = workbook.GetSheetAt(index);
if (sheet != null)
{
DataTable table = GenerateTableData(sheet);
model.Tables.Add(table);
}
}
return model;
}
}
This does require the NPOI nuget package to be installed in your project.
Any questions give me a shout. The github project does a bit more but this is enough to get you going hopefully.
I'm trying to add a formula to cell but i got the error
Exception from HRESULT: 0x800A03EC
There are lots of posts with similar issues however none could help me plus i'm not doing any fancy formula's what i'm doing wrong?
Thread.CurrentThread.CurrentCulture =
new System.Globalization.CultureInfo("en-US");
workbook = application.Workbooks.Open(Helper.GetLocalInstalationFolder() +
#"\IMC.xltx", 0, false, 5, "", "", true, XlPlatform.xlWindows, "\t", false,
false, 0, true, 1, 0);
worksheet = workbook.Worksheets["Report"];
var rowValue = 0;
for (int i = 2; i <= LastRow; i++)
{
rowValue = i - 1;
for (int j = 1; j <= 37; j++)
{
worksheet.Cells[i, j] = MyArray[rowValue, j];
}
// I tried all the following all give the same exception:
worksheet.Range[i, 38].Formula = "=3+4";
worksheet.get_Range("R" + i + "C38").FormulaR1C1 = "=3+4";
worksheet.Range[i, 38].FormulaR1C1 = "=3+4";
worksheet.get_Range("R" + i + "C38").Formula = "=3+4";
}
It is a crappy exception and doesn't mean anything more than you slamming Excel with processing requests at a rate that it cannot keep up with. Your program essentially looks like a hyper-active user that's entering formulas at a rate of one per microsecond.
The workaround is to go slower by intentionally sleeping or to force Excel to do less work. You will very probably fix it in this case by assigning the Application.Calculation property. Set it to manual before you start putting formulas into cells. And back to auto after you're done.
More good advice in this blog post.
Perhaps this brings you into the right direction->
[a link] (http://www.codeproject.com/Questions/470089/Exception-from-HRESULT-0x800A03EC-Error)
in my case I was missing double-quotes in the HYPERLINK formula arguments, i.e. the formula itself was wrong. i tried a valid formula, like ..Cell(x,y).Formula = "=MIN(2)" and it worked, therefore that was the case..
Hans Passant is correct, but there may be additional settings needed to enable Excel's ability to handle the cadence of your code operations.
Here's a good set of options to make Excel capable of processing requests faster: Turn Automatic Calculations Off/On
A summary of what worked for me:
using Excel = Microsoft.Office.Interop.Excel;
public class ExcelAppWrapper : IDisposable
{
private Excel.Application _application;
public ExcelAppWrapper()
{
_application = new Excel.Application { Visible = true };
_application.Workbooks.Add(Missing.Value);
//there must be a workbook before setting Application.Calculation
ConfigureApplication(false);
}
public void Dispose()
{
ConfigureApplication(true);
}
private void ConfigureApplication(bool enable)
{
_application.Calculation = enable ? XlCalculation.xlCalculationAutomatic : XlCalculation.xlCalculationManual;
_application.EnableEvents = enable;
_application.ScreenUpdating = enable;
_application.DisplayStatusBar = enable;
}
}
I'm looking for some advice. I'm building on an additional feature to a C# project that someone else wrote. The solution of the project consists of an MVC web application, with a few class libraries.
What I'm editing is the sales reporting function. In the original build, a summary of the sales reports were generated on the web application. When the user generates the sales report, a Reporting class is called in one of the C# class libraries. I'm trying to make the sales reports downloadable in an excel file when the user selects a radio button.
Here is a snippet of code from the Reporting class:
public AdminSalesReport GetCompleteAdminSalesReport(AdminSalesReportRequest reportRequest)
{
AdminSalesReport report = new AdminSalesReport();
string dateRange = null;
List<ProductSale> productSales = GetFilteredListOfAdminProductSales(reportRequest, out dateRange);
report.DateRange = dateRange;
if (titleSales.Count > 0)
{
report.HasData = true;
report.Total = GetTotalAdminSales(productSales);
if (reportRequest.Type == AdminSalesReportRequest.AdminSalesReportType.Complete)
{
report.ProductSales = GetAdminProductSales(productSales);
report.CustomerSales = GetAdminCustomerSales(productSales);
report.ManufacturerSales = GetAdminManufacturerSales(productSales);
if (reportRequest.Download)
{
FileResult ExcelDownload = GetExcelDownload(productSales);
}
}
}
return report;
}
So as you can see, if reportRequest.Download == true, the class should start up the process of creating the excel file. All the GetAdminSales functions do it use linq queries to sort out the sales if they are being displayed on the webpage.
So I have added this along with the GetAdminSales functions:
private FileResult GetExcelDownload(List<TitleSale> titleSales)
{
CustomisedSalesReport CustSalesRep = new CustomisedSalesReport();
Stream SalesReport = CustSalesRep.GenerateCustomisedSalesStream(productSales);
return new FileStreamResult(SalesReport, "application/ms-excel")
{
FileDownloadName = "SalesReport" + DateTime.Now.ToString("MMMM d, yyy") + ".xls"
};
}
and to format the excel sheet, I'm using the NPOI library, and my formatter class is laid out like so:
public class CustomisedSalesReport
{
public Stream GenerateCustomisedSalesStream(List<ProductSale> productSales)
{
return GenerateCustomisedSalesFile(productSales);
}
private Stream GenerateCustomisedSalesFile(List<ProductSale> productSales)
{
MemoryStream ms = new MemoryStream();
HSSFWorkbook templateWorkbook = new HSSFWorkbook();
HSSFSheet sheet = templateWorkbook.CreateSheet("Sales Report");
HSSFRow dataRow = sheet.CreateRow(0);
HSSFCell cell = dataRow.CreateCell(0);
cell = dataRow.CreateCell(0);
cell.SetCellValue(DateTime.Now.ToString("MMMM yyyy") + " Sales Report");
dataRow = sheet.CreateRow(2);
string[] colHeaders = new string[] {
"Product Code",
"Product Name",
"Qty Sold",
"Earnings",
};
int colPosition = 0;
foreach (string colHeader in colHeaders)
{
cell = dataRow.CreateCell(colPosition++);
cell.SetCellValue(colHeader);
}
int row = 4;
var adminTotalSales = GetAdminProductSales(productSales);
foreach (SummaryAdminProductSale t in adminTotalSales)
{
dataRow = sheet.CreateRow(row++);
colPosition = 0;
cell = dataRow.CreateCell(colPosition++);
cell.SetCellValue(t.ProductCode);
cell = dataRow.CreateCell(colPosition++);
cell.SetCellValue(t.ProductName);
cell = dataRow.CreateCell(colPosition++);
cell.SetCellValue(t.QtySold);
cell = dataRow.CreateCell(colPosition++);
cell.SetCellValue(t.Total.ToString("0.00"));
}
}
templateWorkbook.Write(ms);
ms.Position = 0;
return ms;
}
Again like before, the GetAdminSales (GetAdminProductSales, etc) are contained in the bottom of the class, and are just linq queries to gather the data.
So when I run this, I don't get any obvious errors. The summary sales report appears on screen as normal but no excel document downloads. What I have done, which may be putting this off is in my class library I have referened the System.Web.Mvc dll in order to download the file (I have not done it any other way before - and after reading up on the net I got the impression I could use it in a class library).
When I debug through the code to get a closer picture of what's going on, everything seems to be working ok, all the right data is being captured but I found that from the very start - the MemoryStream ms = new Memory Stream declaration line in my formatter class shows up this (very hidden mind you) :
ReadTimeout '((System.IO.Stream)(ms)).ReadTimeout'
threw an exception of type
'System.InvalidOperationException' int
{System.InvalidOperationException}
+{"Timeouts are not supported on this stream."} System.SystemException
{System.InvalidOperationException}
I get the same for 'WriteTimeout'...
Apologies for the long windedness of the explaination. I'd appreciate it if anyone could point me in the right direction, either to solve my current issue, or an alternative way of making this work.
Without getting bogged down in the details, the obvious error is that in GenerateCustomisedSalesFile you create a MemoryStream ms, do nothing with it, then return it.
I created a program a while ago using C# that does some automation for a completely different program, but found that I need to access data from a Lotus Notes database. The only problem is, I can only seem to figure out how to open the database by the server's name (using session.GetDatabase())... I can't figure out how to open it by Replica ID. Does anyone know how I would go about that? (I don't want my program going down every time the server changes.)
public static string[] GetLotusNotesHelpTickets()
{
NotesSession session = new NotesSession();
session.Initialize(Password);
// 85256B45:000EE057 = NTNOTES1A Server Replica ID
NotesDatabase database = session.GetDatabase("NTNOTES1A", "is/gs/gshd.nsf", false);
string SearchFormula = string.Concat("Form = \"Call Ticket\""
, " & GroupAssignedTo = \"Business Systems\""
, " & CallStatus = \"Open\"");
NotesDocumentCollection collection = database.Search(SearchFormula, null, 0);
NotesDocument document = collection.GetFirstDocument();
string[] ticketList = new string[collection.Count];
for (int i = 0; i < collection.Count; ++i)
{
ticketList[i] = ((object[])(document.GetItemValue("TicketNumber")))[0].ToString();
document = collection.GetNextDocument(document);
}
document = null;
collection = null;
database = null;
session = null;
return ticketList;
}
This code is working fine, but if the server changed from NTNOTES1A, then nothing is going to work anymore.
you'll need to use the notesDbDirectory.OpenDatabaseByReplicaID(rid$) method. To get the NotesDbDirectory, you can use the getDbDirectory method of the session
Set notesDbDirectory = notesSession.GetDbDirectory( serverName$ )
So you can use the code below to get a database by replicaID.
public static string[] GetLotusNotesHelpTickets()
{
NotesSession session = new NotesSession();
session.Initialize(Password);
Set notesDBDirectory = session.GetDbDirectory("NTNOTES1A")
// 85256B45:000EE057 = NTNOTES1A Server Replica ID
NotesDatabase database = notesDBDirectory.OpenDatabaseByReplicaID("85256B45:000EE057")
string SearchFormula = string.Concat("Form = \"Call Ticket\""
, " & GroupAssignedTo = \"Business Systems\""
, " & CallStatus = \"Open\"");
NotesDocumentCollection collection = database.Search(SearchFormula, null, 0);
NotesDocument document = collection.GetFirstDocument();
string[] ticketList = new string[collection.Count];
for (int i = 0; i < collection.Count; ++i)
{
ticketList[i] = ((object[])(document.GetItemValue("TicketNumber")))[0].ToString();
document = collection.GetNextDocument(document);
}
document = null;
collection = null;
database = null;
session = null;
return ticketList;
}
Unfortunately, this only solves half of your problem. I know you'd rather just tell Notes to fetch the database with a particular replicaID from the server closest to the client, just like the Notes Client does when you click on a DBLink or Bookmark. However, there is (or appears to be) no way to do that using the Notes APIs.
My suggestion is to either loop through a hard-coded list of potential servers by name, and check to see if the database is found (the OpenDatabaseByReplicaID method returns ERR_SYS_FILE_NOT_FOUND (error 0FA3) if the database is not found). If that's not a good option, perhaps you can easily expose the servername in an admin menu of your app so it can be changed easily if the server name changes at some point.
set database = new NotesDatabase("")
call database.OpenByReplicaID("repid")