Adding a formula to Excel worksheet results in HRESULT: 0x800A03EC - c#

I've searched for an appropriate solution online but couldn't find anything helpful...
In an Excel worksheet I need to assign some values from a database table and then add a formula to next to each value (depending on another Excel worksheet in the same workbook). Adding the data works perfectly but adding the formula results in an error.
I'm getting the data and adding it to the sheet like this:
using (SqlConnection conn = new SqlConnection("MyConnectionString"))
using (SqlCommand comm = new SqlCommand("SELECT DISTINCT [MyField] FROM [MyTable]", conn)
{
conn.Open();
using (SqlDataReader reader = comm.ExecuteReader())
{
myStringList.Add("MyField");
if (reader.HasRows)
while (reader.Read())
myStringList.Add(reader.GetString(reader.GetOrdinal("MyField")));
}
}
workbook.Worksheets.Add(After: workbook.Worksheets[workbook.Sheets.Count]);
for (int counter = 1; counter <= myStringList.Count(); counter++)
((Excel.Worksheet)workbook.ActiveSheet).Cells[counter, 1] = myStringList[counter-1];
So far so good. Now I get to my problem. I need to add a formula to the cells B2, B3, ... for every used cell in column A. The difficulty is that I want to do it with a for loop because the formula depends on the column A.
for (int counter = 2; counter <= myStringList.Count(); counter++)
((Excel.Worksheet)workbook.ActiveSheet).Range["B" + counter].Formula
= $"=VLOOKUP(A{counter};MyOtherWorksheet!$B$2:$B${numberOfRows};1;FALSE)";
numberOfRows is the number of rows in column B in MyOtherWorksheet (it returns the correct number in debugger, so that's not the problem).
But when I assign the formula like this, I'm getting the following exception without any helpful message:
HRESULT: 0x800A03EC
I tried changing .Range["B" + counter] to .Cells[counter, 2] and even tried using .FormulaR1C1 instead of .Formula but I got the same exception.
What am I missing?

I've found the problem. I had to change .Formula to .FormulaLocal.
MSDN description for .FormulaLocal:
Returns or sets the formula for the object, using A1-style references in the language of the user. Read/write Variant.
MSDN description for .Formula:
Returns or sets a Variant value that represents the object's formula in A1-style notation and in the macro language.

Related

Problem with format in C# with DataGridView and TextBox

I have a problem with format in C#.
I have a DataGridView and a TextBox. In this datagridview, there is a column: the single price (format int).
I want sum every elements of single price's column and insert the result into this textbox, but Visual Studio gives me a problem with format of string input ("Format of input's string is not correct").
this is the code than i used:
int TOT = 0;
for (int i = 0; i < dataGridView3.Rows.Count; i++)
{
TOT = TOT + Convert.ToInt32(dataGridView3.Rows[i].Cells[6].ToString());
}
textBoxTot.Text = Convert.ToString(TOT);
Can you help me with this bad error?
UPDATE:
I think that the problem now is another. I can't find the methods of MySql.Data.MySqlClient library that it can give me the result of query.
MySqlCommand command = new MySqlCommand();
String sumQuery = "SELECT SUM(`prezzo`) FROM `fatturetemp`";
command.CommandText = sumQuery;
command.Connection = conn.getConnection();
command.Parameters.Add("#prezzo", MySqlDbType.Int32).Value = costo;
conn.openConnection();
conn.closeConnection();
How is the command that give me the result of sumQuery. If i find this command, i can take the result of query and paste in textbox
If your datagridview is showing a datatable (I.e. your data is stored in a datatable) you can add a DataColumn to the datatable whose .Expression property is set to the string "SUM([Price])", where Price is the name of your numeric datatyped column holding the price info.
Now every row in the table will have the sum of the prices (the same value over and over again), so if you want to databind your textBox to this new column then it will always show the sum no matter which row is the current row (because all rows have the same value). It will also auto update without you having to do anything!
And if you're not using databinding to a datatable, I recommend that you do do it, because it represents good MVC practice of keeping your data in one thing (DataTable) and showing it in another (DataGridView) and keeping these concerns separate
It would look something like this, as a quick example:
DataTable dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Price", typeof(int));
dt.Columns.Add("CalculatedTotal", typeof(int)).Expression = "SUM([Price])";
dt.Rows.Add("Apples", 100);
dt.Rows.Add("Oranges", 200);
BindingSource bs = new BindingSource();
bs.DataSource = dt;
WhateverDataGridView.DataSource = bs;
totalTextBox.DataBindings.Add(new Binding("Text", bs, "CalculatedTotal", true));
Here we have a data model (the datatable) where we keep our data. It has a couple of things we can set directly, and an third column that calculates based on the existing prices in all the table. If you look at this in the datagridview (assuming you have autogeneratecolumns turned on) you'll see that both rows have 300 for the CalculatedTotal, but they have individual amounts for price. There is a device called a BindingSource that sits between the datatable and the UI controls; you don't have to have one but it makes certain things easier regards updating controls when the data changes, and it maintains a concept of "current" - essentially whatever the current row is you're looking at in the datagridview; it all helps to avoid having to ask the DGV for anything - we just let the user type into the DGV, and it shows the data out of the datatable. All our dealings can be with the datatable directly - if you wrote a button to loop through the table and double all the prices, the controls in the UI would just reflect the change automatically. The textbox is connected to the CalculatedValue column via databinding; whatever the current row is, the textbox will show the CalculatedValue. Because the CalculatedValue column has the same value on every row, and they always all update if any price changes, the total textbox will always show the total. Add another textbox bound to Name to see what I mean; as you click around the grid and select different rows to be the "Current" row, the Name will change but the total does not. In truth it is actually changing in the same way that Name is, it's just that because the actual numeric value is the same on every row the contents of the textbox look like they don't change
UPDATE: I think that the problem now is another. I can't find the methods of MySql.Data.MySqlClient library that it can give me the result of query.
public string sommaFattura(String costo)
{
MySqlCommand command = new MySqlCommand();
String sumQuery = "SELECT SUM(`prezzo`) FROM `fatturetemp`";
command.CommandText = sumQuery;
command.Connection = conn.getConnection();
command.Parameters.Add("#prezzo", MySqlDbType.Int32).Value = costo;
conn.openConnection();
conn.closeConnection();
}
How is the command that give me the result of sumQuery. If i find this command, i can take the result of query and paste in textbox
It is weird that you are first converting to a string and then to an int.
int TOT = 0;
for (int i = 0; i < dataGridView3.Rows.Count; i++)
{
if (!dataGridView3.Rows[i].IsNewRow &&
int.TryParse(dataGridView3.Rows[i].Cells[6].Value.ToString(), out int v))
TOT += v;
}
textBoxTot.Text = TOT.ToString();
EDIT: Edited for your updated question. You shouldn't ask question inside a question buy anyway:
string sumQuery = "SELECT SUM(`prezzo`) FROM `fatturetemp`";
decimal total = 0M;
using (MySqlConnection cn = new MySqlConnection(" your connection string here "))
using (MySqlCommand cmd = new MySqlCommand(sumQuery, cn))
{
cn.Open();
total = Convert.ToDecimal(cmd.ExecuteScalar());
cn.Close();
}
Console.WriteLine(total);

DataTable ColumnName Limitations?

I'm trying to read data from an Excel File into a DataSet, but my problem is the DataTable Column Names aren't matching what's in my Excel. It seems to be only Columns with long Column Names. In the DataTable Column Names it cuts off at some point.
Here's my attempt:
DataSet mainExcelDataToImport = new DataSet();
using (OleDbConnection mainExcelOleDbConnection = new OleDbConnection())
{
string theMainExcelConnectionString = this.ExcelConnectionString.Replace("{FullFilePath}", this.SelectedFilePath);
mainExcelOleDbConnection.ConnectionString = theMainExcelConnectionString;
// Open
mainExcelOleDbConnection.Open();
string mainExcelSQL = "SELECT * FROM [{ExcelSheet}$]";
mainExcelSQL = mainExcelSQL.Replace("{ExcelSheet}", selectedExcelSheet);
using (OleDbCommand mainExcelOleDbCommand = mainExcelOleDbConnection.CreateCommand())
{
mainExcelOleDbCommand.CommandText = mainExcelSQL;
mainExcelOleDbCommand.CommandType = CommandType.Text;
// Prepare
mainExcelOleDbCommand.Prepare();
using (OleDbDataAdapter mainOleDbDataAdapter = new OleDbDataAdapter(mainExcelOleDbCommand))
{
mainOleDbDataAdapter.Fill(mainExcelDataToImport);
// Close
mainExcelOleDbConnection.Close();
}
}
}
Here's a .xlsx I've tried:
A1234567890123456789012345678901234567890123456789012345678901234567890
TEST
'A12345.....' is the Column Name at A1
'TEST' is the Value at A2
When I check the `ColumnName` in the `DataTable` I get: 'A123456789012345678901234567890123456789012345678901234567890123' which is 64 characters.
How does the ColumnName MaxLength limitation work?
Can I get rid of it?
Is it maybe a limitation on the OleDbDataAdapter?
Any help / suggestions would be appreciated.
64 characters is unusually long for the name of a database column. I've never seen a column name come anywhere near that length.
It seems odd that you would have a column name so long.
They have to be stored somewhere of course and so there is likely to be a maximum on any database software eg SQL server has a maximum of 128. It would not surprise me if intermediate software like sqlclient had a lower limit. Very long names are so unusual.
It's probably a limitation of the adapter.
You could try some of the alternatives mentioned in this thread:
Best /Fastest way to read an Excel Sheet into a DataTable?
Or maybe you can just work with xml - and not a datatable.
Xml will be able to handle any size field that excel can.
https://learn.microsoft.com/en-us/office/open-xml/understanding-the-open-xml-file-formats
https://learn.microsoft.com/en-us/office/open-xml/how-to-parse-and-read-a-large-spreadsheet
I

OutOfMemoryException while trying to read big Excel file into DataTable

I'm using SSIS package to clean and load data from .Xlsx file to SQL Server table.
I have also to highlight cells containing wrong data in .Xlsx file, for this I have to get back column and row indexes based on column name and row id(witch I have in my data spreadsheet). For that I compare each column name from my first spreadsheet (Error_Sheet) with rows of a column that I added in a second spreadsheet and do the same for rows, and if I have the same value of cells I get back the column and row indexes of my data spreadsheet and highlight the cell based on that column and row index. The script worked fine, but after trying to run it from a server I got an Memory exception and also on my workstation where it was working fine before.
I've tried to reduce the range that I'm taking data from : AC1:AC10000 to AC1:AC100, it worked only after the first time compilation, but it keeps throwing exception again.
string strSQLErrorColumns = "Select * From [" + Error_Sheet + "AC1:AC100]";
OleDbConnection cn = new OleDbConnection(strCn);
OleDbDataAdapter objAdapterErrorColumns = new OleDbDataAdapter(strSQLErrorColumns, cn);
System.Data.DataSet dsErrorColumns = new DataSet();
objAdapterErrorColumns.Fill(dsErrorColumns, Error_Sheet);
System.Data.DataTable dtErrorColumns = dsErrorColumns.Tables[Error_Sheet];
dsErrorColumns.Dispose();
objAdapterErrorColumns.Dispose();
foreach (DataColumn ColumnData in dtDataColumns.Columns){
ColumnDataCellsValue = dtDataColumns.Columns[iCntD].ColumnName.ToString();
iCntE = 0;
foreach (DataRow ColumnError in dtErrorColumns.Rows){
ColumnErrorCellsValue = dtErrorColumns.Rows[iCntE].ItemArray[0].ToString();
if (ColumnDataCellsValue.Equals(ColumnErrorCellsValue)){
ColumnIndex = ColumnData.Table.Columns[ColumnDataCellsValue].Ordinal;
iCntE = iCntE + 1;
break;
}
}
iCntD = iCntD + 1;
}
ColumnIndexHCell = ColumnIndex + 1;
RowIndexHCell = RowIndex + 2;
Range rng = xlSheets.Cells[RowIndexHCell, ColumnIndexHCell] as Excel.Range;
rng.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Yellow);
There is any other way to load data in DataTable to get column and row index without using a lot of memory or by using Excel.Range.Cell instead of dataset and DataTable to get Cell value, column and row index from xlsx file please ?
I didn't show the whole code because it's long. Please keep me informed if more information needed.
When trying to read data from an Excel with huge number of Rows, it is better to read data by chunk (in OleDbDataAdapter you can use paging option to achieve that).
int result = 1;
int intPagingIndex = 0;
int intPagingInterval = 1000;
while (result > 0){
result = daGetDataFromSheet.Fill(dsErrorColumns,intPagingIndex, intPagingInterval , Error_Sheet);
System.Data.DataTable dtErrorColumns = dsErrorColumns.Tables[Error_Sheet];
//Implement your logic here
intPagingIndex += intPagingInterval ;
}
This will prevent an OutOfMemory Exception. And no more need to specify a range like AC1:AC10000
References
Paging Through a Query Result
Fill(DataSet, Int32, Int32, String)

How do you get the name of the first page of an excel workbook?

Suppose you don't know the name of the first worksheet in an excel workbook. And you want to find a way to read from the first page. This snippet sometimes works, but not always. Is it just me? Or is there a no brainer way to do this?
MyConnection = new System.Data.OleDb.OleDbConnection("provider=Microsoft.Jet.OLEDB.4.0;Data Source='" + inputFile + "';Extended Properties=Excel 8.0;");
String[] excelSheets = new String[tbl.Rows.Count];
int i = 0;
foreach (DataRow row in tbl.Rows)
{
excelSheets[i] = row["TABLE_NAME"].ToString();
i++;
}
string pageName = excelSheets[0];
OleDbDataAdapter myAdapter = new System.Data.OleDb.OleDbDataAdapter("SELECT * FROM [" + pageName + "]", MyConnection);
Note: I am looking for the name of the first worksheet.
If you have Office installed on the machine, why not just use Visual Studio Tools for Office (VSTO). Here is essentially the code to get the worksheet:
Microsoft.Office.Interop.Excel.Application app = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Workbook workbook = app.Workbooks.Open(fileName,otherarguments);
Microsoft.Office.Interop.Excel.Worksheet worksheet = workbook.Worksheets[1] as Microsoft.Office.Interop.Excel.Worksheet;
Your code seems to be missing the defintion of tbl. I assume it is something like
DataTable tbl = MyConnection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
If so, you will probably get the sheetnames but in the wrong order.
I could not find a proper solution for this issue, so I approached it from another point of view. I decided to look for sheets that actual had information on it. You can probably do this by looking at the rows, but the method I used was to look at the columns from the schema information. (This obviously will fail in your used sheet only has one column as unused sheets also have one column), but it worked in my case, and I also used it to check I had the expected number of columns (in my case nine)
This uses the GetOleDbSchemaTable(OleDbSchemaGuid.Columns, null) method to return the column information.
The code is probably irrelevant/trival, and as I happened to be learning LINQ when I came across this issue, so I wrote it in LINQ style
It does require a small class called LinqList which you can get here
DataTable columnDetails = objConn.GetOleDbSchemaTable(
System.Data.OleDb.OleDbSchemaGuid.Columns, null);
LinqList<DataRow> rows = new LinqList<DataRow>(columnDetails.Rows);
var query= (from r in rows
group r by r["Table_Name"] into results
select new { results.Key , count=results.Count() }
);
var activeSheets = (from sheet in query
where sheet.count == 9
select sheet.Key
).ToList();
if (activeSheets.Count != 1)
... display error
This is the same as this other question First sheet Excel
I think that the order of the returned table gets messed up. We would need to find a way to get the order of the tabs. For now if you check your code, sometime the first sheet is index 0. But it can be returned in any order. I have tried deleting the other sheets and with only one you get the right name. But that wouldn't be pratical.
edit : after some research, it could be the tabs are returned in order of names Using Excel OleDb to get sheet names IN SHEET ORDER
see link
SpreadsheetGear for .NET will let you load a workbook and get the names of sheets (with IWorkbook.Worksheets[sheetIndex].Name) and get the raw data or formatted text of each cell (it does more but that's probably what you are looking for if you are currently using OleDB).
You can download a free trial here.
Disclaimer: I own SpreadsheetGear LLC

Excel DateTime being returned as DBNull

I have some Excel file reading code that uses the OLEDB (Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=Excel 8.0;) which works well but I keep encountering an issue whereby certain dates are returned as DBNull.
In the original XLS document, the format of dates that work (en-GB locale) are:
"02/04/2009 17:00:00" // returned as a System.DateTime
And the following style fails:
"08/Jan/09 11:24 AM" // returned as DBNull
Excel knows they're both dates (although I can't force them to style correctly) as the following correctly shows a date:
=DATE(YEAR(c),MONTH(c),DAY(c)) // where c = cell reference.
Is there a way, without altering the auto-generated original, to get the data?
EDIT for reference, here is my read-data method (assuming a dbAdapter is set up already -- note the DBNull doesn't come from the catch which isn't fired at all):
public List<List<string>> GetData(string tableName, int maxColumns)
{
List<List<string>> rows = new List<List<string>>();
DataSet ExcelDataSet = new DataSet();
dbCommand.CommandText = #"SELECT * FROM [" + tableName + "]";
dbAdapter.Fill(ExcelDataSet);
DataTable table = ExcelDataSet.Tables[0];
foreach (DataRow row in table.Rows)
{
List<string> data = new List<string>();
for (int column = 0; column < maxColumns; column++)
{
try
{
data.Add(row[column].ToString());
}
catch (Exception)
{
data.Add(null);
}
}
// Stop processing at first blank row
if ( string.IsNullOrEmpty(data[0]) ) break;
rows.Add(data);
}
return rows;
}
I don't know if this will be helpful or not, but I have run into issues with Excel OLEDB code returning NULLs where I expected data and it almost always came back to a data type inference issue. Excel determines the datatype of a column based on the first x rows of data (I think x=10, could be wrong). I know you don't want to alter the file, but it might be worth trying to put the problem date style in the first 10 rows and see if it alters the behavior of your application.
Obviously if it does fix it, then that doesn't solve your problem. The only fixes in that case that I know of are to alter the file (put something in the first 10 rows that forces it to use the correct datatype). Sorry I can't offer a better solution, but hopefully at least I am helping you figure out what's causing your issue.

Categories