I parsed through an Excel spreadsheet and returned the entire result as a DataTable. However, this Excel spreadsheet has several empty rows that I would like to eliminate from the resulting DataTable. In particular, each empty row begins with an empty cell. So, I know that the entire row is empty if the value of the cell at the first index is empty. Note that I cannot simply modify the Excel spreadsheet because I have to work with exactly what the client has sent to me. Based on this information, I assumed that I could perform the following function to remove empty rows:
' DataTable dt has already been populated with the data
For Each row As DataRow In dt.Rows
If dt.Rows.Item(0).ToString = "" Then
dt.Rows.Remove(row)
ElseIf dt.Rows.Item(0) Is Nothing Then
dt.Rows.Remove(row)
End If
Next
However, after crafting this solution, I am greeted with the following error:
Collection was modified; enumeration operation might not execute.
I now realize that I cannot alter the collection as I access it. How can I get around this? I'm wondering if I should create a new DataTable with the rows that aren't empty. Is this the best approach or are there better options?
EDIT: I have also tried iterating over the rows backwards:
For i = dt.Rows.Count() - 1 To 0
If dt.Rows.Item(i).Item(0).ToString = "" Then
dt.Rows.RemoveAt(i)
End If
Next
You can't modify the collection while you're enumerating it with For Each, but a simple For loop will work. You'll need to loop backwards to avoid skipping the row after a removed row.
You've also got your tests the wrong way round; if Item(0) returns Nothing, then Item(0).ToString will throw a NullReferenceException.
I'm assuming the dt.Rows.Item(0) is a typo, and should read row.Item(0) instead.
For i As Integer = dt.Rows.Count - 1 To 0 Step -1
Dim row As DataRow = dt.Rows(i)
If row.Item(0) Is Nothing Then
dt.Rows.Remove(row)
ElseIf row.Item(0).ToString = "" Then
dt.Rows.Remove(row)
End If
Next
Vb.Net using linq
Dtset.Tables(0).AsEnumerable().Where(Function(row) row.ItemArray.All(Function(field) field Is Nothing Or field Is DBNull.Value Or field.Equals(""))).ToList().ForEach(Sub(row) row.Delete())
Dtset.Tables(0).AcceptChanges()
Related
I have a data table that is loaded from an excel spreadsheet, which works fine. I then want to remove the duplicates from the table, which I can also accomplish. Next I want to add new rows which is where the whole thing goes wonky. The code runs and no errors are returned. (I have Try/Catch statements in place and they never get triggered).
Here is the code that tries to add the new row to the data table.
newDRow = dtTemp.NewRow
newDRow("QTY") = Convert.ToInt32(dupList(1))
newDRow("GRATING-ID") = newMarkNum
newDRow("GRATING SPAN DECIMAL INCHES") = Convert.ToDecimal(dupList(2))
newDRow("GRATING WIDTH DECIMAL INCHES") = Convert.ToDecimal(dupList(3))
Try
dtTemp.Rows.Add(newDRow)
Catch ex As Exception
message = HttpUtility.JavaScriptStringEncode(ex.Message)
End Try
The data table is called dtTemp. I have put break points in and can verify that each of the new row columns are getting the correct data.
The original data table is being passed to this function as follows:
Private Function RemoveDuplicate(ByVal dtInput As DataTable) As DataTable
I have a line that copies the dtInput table to the dtTemp table like so:
Dim dtTemp As DataTable = dtInput.Copy()
I just can't seem to find out why the row insert is not working. When the function runs and the data grid view is populated I see the results of removing the duplicate lines but the new lines are not in the table.
Any ideas?
Update #1--------------
I checked the table contents after the inserts were supposed to have happened but they do not show up. there should be a line that says:
2|1121-G31|32.875|42
And a line that says
7|1185-G33|22|22
But they are not there.
So I figured out what stupid mistake I was doing.
I had my new data row statement OUTSIDE of the For loop that was adding the new rows to the data table.
Moved it inside the for loop and it works great now!
I'm producing a report in Crystal Reports where the column order must match the order that they're displayed in in the SSDBGrid in VB6.
To do this, I decided it was best to loop over each column in the grid, and call a function to set the parameter field values to the correct heading (so, column 0's caption would be the value for parameter field #Col1, etc)
Anyway, the code I have for this is
Dim c As Column
Dim cName As String
For Each c In FShow_All_Accounts.grd_accounts.Columns
cName = "#Col" & c.ListIndex
Call setColumnHeaders(c.Index)
crxReport.ParameterFields.GetItemByName(cName).AddCurrentValue ("f")
Next
The problem is that first of all, setting the type of c to Column and looping over each c in grd_accounts seems to be incorrect - grd_accounts is an SSDBGrid, and secondly, it errors when trying to return the index.
So, my question(s):
What is the correct way to loop over each column in an SSDBGrid?
Secondly, how would I then get the column index for the correct column, to pass into the function?
The answer is actually more simple than you're imagining it to be. Your code is very nearly there.
Dim c As Column
Dim i As String
Dim cname As String
i = 0
For Each c in grd_accounts.Columns
i = i + 1
cName = "#Col" & i
crxReport.ParameterFields.GetItemByName(cName).AddCurrentValue (c.Caption)
Next
No need for the helper function. This will iterate over each column, set cName to be "#Col" and the value of i (Also the column number), and therefore that parameter field in the report will be captioned with that columns caption.
I'm really new to the use of closedXMl and Excel too(at least for this purpose) so sorry if I'm asking silly questions.
I know that closedXML doesn't support charts yet so the only thing that came to mind to get around this was to create my chart using an excel table . That way I thought ClosedXML would update the ranges when I inserted new rows and the chart would pick up on it. Well , it didn't. At least not when I add the rows from code using the closedXML library.
What is curious is that adding new rows from inside excel automatically updates the chart but if I want to get that same result from code, I have to use OFFSET formula along with named ranges and then set the chart source data to these named ranges.
That's why I'd like to know if if there is anything wrong with the code I use to insert new rows:
Dim ruta As String = Server.MapPath("~/Templates/MyTemplate.xlsx")
Dim wb As New XLWorkbook(ruta)
Dim ws = wb.Worksheet(1)
Dim tblData = ws.Table("Table1")
Dim year As Integer = 2000
For i As Integer = 1 To 13
With tblData.DataRange.LastRow()
.Field("Year").SetValue(year)
.Field("Sales").SetValue(CInt(Math.Floor((2000 - 500 + 1) * Rnd())) + 500)
End With
tblData.DataRange.InsertRowsBelow(1)
year = year + 1
Next
tblData.LastRow.Delete()
As you can see the code is very simple and so is the template , that consists of only two columns : "Year"(table1[Year]) and "Sales"(Table1[Sales]
I don't think this has anything to do with my template because as I told you, adding new rows directly from excel works as expected and it is only when I generate the table from code that the chart series doesn't include the new row that were added
Being necessary to manually add the new ranges(Sheet1!Table1[Sales] and Sheet1!Table1[Year]) as it only includes the first row(the one added by default when you insert a table)
Any help would be much appreciated
P.S. Here is a link to a rar containing the full code as well as the excel template(\Templates\MyTemplate.xlsx)
If the problem is that your table doesn't recognise the additional rows, try adding this after the last row delete:
tblData.Resize tblData.Range(1, 1).CurrentRegion
That should resize the table. Then hopefully your table operations should work.
In web application, I fetch the data from back end and the data is now availbale in dataset. I have 5 columns in that dataset, but i need only one column, so how can i get only one column out of 5 columns. I use dataview but it is not getting.
DataView dv = ds.Tables[0].DefaultView;
dv.RowFilter= "empid";
but i am not getting. can you helpme
With the help of dataview you can create another table which has only required columns which you need
DataView view = new DataView(ds.tables[0]);
DataTable newTable = view.ToTable(tabblename, false, params string[] columnNames
);//Add name of required
You can get full example from the below link
Click
or
You can remove extra columns
ds.Tables[string].Columns.Remove(string)
to remove individual columns which you feel are extra one.
Don't forget to ds.AcceptChanges() after deleting rows or they will still be in the dataset.
You can set the dv.AutoGenerateMembers to false then create on column inside yourDataView (using designer approach) that matches the names of that column you want to show on the DataView
We have an automatic process that opens a template excel file, writes rows of data, and returns the file to the user. This process is usually fast, however I was recently asked to add a summary page with some Excel formulas to one of the templates, and now the process takes forever.
It successfully runs with about 5 records after a few minutes, however this week's record set is almost 400 rows and the longest I've let it run is about half an hour before cancelling it. Without the formulas, it only takes a few seconds to run.
Is there any known issues with writing rows to an Excel file that contains formulas? Or is there a way to tell Excel not to evaluate formulas until the file is opened by a user?
The formulas on the summary Sheet are these:
' Returns count of cells in column where data = Y
=COUNTIF(Sheet1!J15:Sheet1!J10000, "Y")
=COUNTIF(Sheet1!F15:Sheet1!F10000, "Y")
' Return sum of column where data is a number greater than 0
' Column contains formula calculating the difference in months between two dates
=SUMIF(Sheet1!I15:Sheet1!I10000,">0",Sheet1!I15:Sheet1!I10000)
' Returns a count of distinct values in a column
=SUMPRODUCT((Sheet1!D15:Sheet1!D10000<>"")/COUNTIF(Sheet1!D15:Sheet1!D10000,Sheet1!D15:Sheet1!D10000&""))
And the code that writes to excel looks something like this:
Dim xls as New Excel.Application()
Dim xlsBooks as Excel.Workbooks, xlsBook as Excel.Workbook
Dim xlsSheets as Excel.Sheets, xlsSheet as Excel.Worksheet
Dim xlsCells as Excel.Range
xls.Visible = False
xls.DisplayAlerts = False
xlsBooks = xls.Workbooks
xlsBooks.Open(templateFile)
xlsBook = xlsBooks.Item(1)
' Loop through excel Sheets. Some templates have multiple sheets.
For Each drSheet as DataRow in dtSheets.Rows
xlsSheets = xlsBook.Worksheets
xlsSheet = CType(xlsSheets.Item(drSheet("SheetName")), Excel.Worksheet)
xlsCells = xlsSheet.Cells
' Loop though Column list from Database. Each Template requires different columns
For Each drDataCols as DataRow in dtDataCols.Rows
' Loop though Rows to get data
For Each drData as DataRow in dtData.Rows
xlsCells(drSheet("StartRow") + dtData.Rows.IndexOf(drData), drDataCols("DataColumn")) = drData("Col" + drDataCols("DataColumn").toString).toString
Next
Next
Next
xlsSheet.SaveAs(newFile)
xlsBook.Close
xls.Quit()
Every time you write to a cell Excel recalculates the open workbooks and refreshes the screen. Both of these things are slow, so you need to set Application.Screenupdating=false and Application.Calculation=xlCalculationManual
Also there is a high overhead associated with each write to a cell, so it is much faster to acuumulate the data in an array and then write the array to the range with a single call to the Excel object model.
With auto mode calculation, recalculation occurs after every data input/changed. I had the same problem, was solved by setting Manual calculation mode. (Reference MSDN link.)
xls.Calculation = Excel.XlCalculation.xlCalculationManual
Also, this property can only be set after a Workbook has been opened or it will throw a run-time error.
One way that has saved me over the years is to add
Application.ScreenUpdating = False
directly before I execute a potentially lengthy method, and then
Application.ScreenUpdating = True
directly after, or at least at some later point in the code. This forces Excel to not redraw anything on the visible screen until it is complete That issue is where I've found lengthy running operations to stem from quite often.