Excel column name may contain trailing spaces which would hit exception if the column don't match due to spaces.
I am finding ways to handle trailing spaces in the column name in datatable.
foreach (DataRow row in caseTable.Rows)
{
foreach (DataColumn column in caseTable.Columns)
{
if (!(string.isNullOrEmpty(column.toString())))
{
//Cannot assign value to 'column' because it is in a 'foreach iteration variable'
column = column.ToString().TrimStart().TrimEnd();
trimmed = 1;
}
}
while (trimmed == 0) ;
}
...
//errored out due to 'Excel.firstName' value not existing in DataTable due to trailing spaces
if (row[Excel.firstName].ToString().Trim() != "")
{
caseEntity.Attributes[Case.firstName] = row[Excel.firstName];
}
There are two mistakes in your code:
You are iterating a sequence with foreach and trying to modify the iterated items; thus, the exception that is throwing.
You are trying to replace the column object instead of its name.
Additionally, you are abusing ToString and, as already stated by Michal Turczyn, you are not using built-in String methods explicitly designed to test for empty or null strings.
You can try replacing the code inside the inner loop with
var oldName = column.ColumnName;
if (!string.IsNullOrEmpty(oldName))
{
var newName = oldName.Trim();
if (newName != oldName)
{
column.ColumnName = newName;
trimmed = 1;
}
}
From this replacement code You can see that:
You should read/write ColumnName property instead of using ToString.
You can avoid invoking TrimStart and TrimEnd one after another (in any order) by using Trim.
You should use string.IsNullOrEmpty instead of checking for null andempty string.
By declaring oldName and newName, you can track a column name as trimmed only when that's really true.
More over, if you are assigning only values 0 and 1 to trimmed variable, then you should consider declaring it as bool and assign false or true.
Otherwise you can keep trimmed variable as a number, but increasing its value rather than assigning always the same constant (1).
If what you are trying to change is not the column name, but is the cell value in the current row for such column, then you are missing to get (and later set) the cell value (you are evaluating and trying to change only the column name).
In that case, the inner loop code become:
var oldValue = row[column] as string;
if (!string.IsNullOrEmpty(oldValue))
{
var newValue = oldValue.Trim();
if (newValue != oldValue)
{
row[column] = newValue;
trimmed = 1;
}
}
Related
UPDATE
I think I have found what is causing the issue here https://stackoverflow.com/a/5665600/19393524
I believe my issue lies with my use of .DefaultView. The post thinks when you do a sort on it it is technically a write operation to the DataTable object and might not propagate changes made properly or entirely. It is an interesting read and seems to answer my question of why passing valid data to a DataRow is throwing this exception AFTER I make changes to the datatable
UPDATE:
Let me be crystal clear. I have already solved my problem. I would just like to know why it is throwing an error. In my view the code should work and it does.. the first run through.
AFTER I have already deleted the column then added it back (run this code once)
When I debug my code line by line in Visiual studio and stop at the line:
data.Rows[i].SetField(sortColumnNames[k], value);
the row exists
the column exisits
value is not null
sortColumnNames[k] is not null and contains the correct column name
i is 0
Yet it still throws an exception. I would like to know why. What am I missing?
Sorry for the long explanation but this one needs some context unfortunately.
So my problem is this, I have code that sorts data in a DataTable object by column. The user picks the column they want to sort by and then my code sorts it.
I ran into an issue where I needed numbers to sort as numbers not strings (all data in the table is strings). eg (string sorting would result in 1000 coming before 500)
So my solution was to create a temporary column that uses the correct datatype so that numbers get sorted properly and the original string data of the number remains unchanged but is now sorted properly. This worked perfectly. I could sort string numeric data as numeric data without changing the formatting of the number or data type.
I delete the column I used to sort afterwards because I use defaultview to sort and copy data to another DataTable object.
That part all works fine the first time.
The issue is when the user needs to do a different sort on the same column. My code adds back the column. (same name) then tries to add values to the column but then I get a null reference exception "Object not set to an instance of an object"
Here is what I've tried:
I've tried using AcceptChanges() after deleting a column but this did nothing.
I've tried using column index, name, and column object returned by DataTable.Columns.Add() in the first parameter of SetField() in case it was somehow referencing the "old" column object I deleted (this is what I think the problem is more than likely)
I've tried changing the value of the .ItemArray[] directly but this does not work even the first time
Here is the code:
This is the how the column names are passed:
private void SortByColumn()
{
if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
{
//clears the datatable object that stores the sorted defaultview
sortedData.Clear();
//grabs column names the user has selected to sort by and copies them to a string[]
string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
lbColumnsToSortBy.Items.CopyTo(lbItems, 0);
//adds temp columns to data to sort numerical strings properly
string[] itemsToSort = AddSortColumns(lbItems);
//creates parameters for defaultview sort
string columnsToSortBy = String.Join(",", itemsToSort);
string sortDirection = cbAscDesc.SelectedItem.ToString();
data.DefaultView.Sort = columnsToSortBy + " " + sortDirection;
//copies the defaultview to the sorted table object
sortedData = data.DefaultView.ToTable();
RemoveSortColumns(itemsToSort);//removes temp sorting columns
}
}
This is where the temp columns are added:
private string[] AddSortColumns(string[] items)//adds columns to data that will be used to sort
//(ensures numbers are sorted as numbers and strings are sorted as strings)
{
string[] sortColumnNames = new string[items.Length];
for (int k = 0; k < items.Length; k++)
{
int indexOfOrginialColumn = Array.IndexOf(columns, items[k]);
Type datatype = CheckDataType(indexOfOrginialColumn);
if (datatype == typeof(double))
{
sortColumnNames[k] = items[k] + "Sort";
data.Columns.Add(sortColumnNames[k], typeof(double));
for (int i = 0; i < data.Rows.Count; i++)
{
//these three lines add the values in the original column to the column used to sort formated to the proper datatype
NumberStyles styles = NumberStyles.Any;
double value = double.Parse(data.Rows[i].Field<string>(indexOfOrginialColumn), styles);
bool test = data.Columns.Contains("QtySort");
data.Rows[i].SetField(sortColumnNames[k], value);//this is line that throws a null ref exception
}
}
else
{
sortColumnNames[k] = items[k];
}
}
return sortColumnNames;
}
This is the code that deletes the columns afterward:
private void RemoveSortColumns(string[] columnsToRemove)
{
for (int i = 0; i < columnsToRemove.Length; i++)
{
if (columnsToRemove[i].Contains("Sort"))
{
sortedData.Columns.Remove(columnsToRemove[i]);
}
}
}
NOTE:
I've been able to fix the problem by just keeping the column in data and just deleting the column from sortedData as I use .Clear() on the sorted table which seems to ensure the exception is not thrown.
I would still like an answer though as to why this is throwing an exception. If I use .Contains() on the line right before the one where the exception is thrown is says the column exists and returns true and in case anyone is wondering the params sortColumnNames[k] and value are never null either.
Your problem is probably here:
private void RemoveSortColumns()
{
for (int i = 0; i < data.Columns.Count; i++)
{
if (data.Columns[i].ColumnName.Contains("Sort"))
{
data.Columns.RemoveAt(i);
sortedData.Columns.RemoveAt(i);
}
}
}
If you have 2 columns, and the first one matches the if, you will never look at the second.
This is because it will run:
i = 0
is i < columns.Count which is 2 => yes
is col[0].Contains("sort") true => yes
remove col[0]
i = 1
is i < columns.Count which is 1 => no
The solution is to readjust i after the removal
private void RemoveSortColumns()
{
for (int i = 0; i < data.Columns.Count; i++)
{
if (data.Columns[i].ColumnName.Contains("Sort"))
{
data.Columns.RemoveAt(i);
sortedData.Columns.RemoveAt(i);
i--;//removed 1 element, go back 1
}
}
}
I fixed my original issue by changing a few lines of code in my SortByColumn() method:
private void SortByColumn()
{
if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
{
//clears the datatable object that stores the sorted defaultview
sortedData.Clear();
//grabs column names the user has selected to sort by and copies them to a string[]
string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
lbColumnsToSortBy.Items.CopyTo(lbItems, 0);
//adds temp columns to data to sort numerical strings properly
string[] itemsToSort = AddSortColumns(lbItems);
//creates parameters for defaultview sort
string columnsToSortBy = String.Join(",", itemsToSort);
string sortDirection = cbAscDesc.SelectedItem.ToString();
DataView userSelectedSort = data.AsDataView();
userSelectedSort.Sort = columnsToSortBy + " " + sortDirection;
//copies the defaultview to the sorted table object
sortedData = userSelectedSort.ToTable();
RemoveSortColumns(itemsToSort);//removes temp sorting columns
}
}
Instead of sorting on data.DefaultView I create a new DataView object and pass data.AsDataView() as it's value then sort on that. Completely gets rid of the issue in my original code. For anyone wondering I still believe it is bug with .DefaultView in the .NET framework that Microsoft will probably never fix. I hope this will help someone with a similar issue in the future.
Here is the link again to where I figured out a solution to my problem.
https://stackoverflow.com/a/5665600
I am having an issue trying to match data table column names with a string containing a wildcard.
I have a data table with various column names which includes a set named like follows: "PsA", "PsB", "PsC", ect. I want to iterate through all the columns containing Ps in the title and use those titles to extract the data.
I currently have the following code which fails to return any matches. I have substituted a straight value in the if statement ("PsA") as a test, which works fine; however, when I use the wildcard, I get no matches. I have also tried Regex with no luck.
private void dfaSection_SelectedIndexChanged(object sender, EventArgs e)
{
string psText = null;
string colID = "Ps*";
offBox.Text = ""; //Clear textbox if a reselection occurs
psBox.Text = ""; //Clear textbox if a reselection occurs
info.offTitle = dfaSection.Text; //Set textbox from variable
dt = opr.findOffByTitle(info); //Get datatable from SQL database
if(dt.Rows.Count > 0)
{
offBox.Text = dt.Rows[0][5].ToString(); //Set textbox from datatable
foreach(DataColumn dc in dt.Columns) //Loop through datatable columns
{
if (dc.ColumnName.ToString() == colID) //Check that column title matches test string - Later addition--> && dt.Rows[0][dc].ToString() != null)
{
psText = dt.Rows[0][dc].ToString() + "\n\n"; //Add data from matched column to string variable
}
}
psBox.Text = psText; //Set textbox from variable
}
}
Edit: Issue fixed using .Contains(colID) Column names now being matched, string are now not being loaded to the psText variable, but I'll spend some time with that and get it working. Thanks Skaros Ilias.
I am not so failiar with C, but == for sure is not the right method.
you need to use the contains method. As I said, I am not familiar with it, but the docs have a good example of it.
String s = "This is a string.";
String sub1 = "this";
Console.WriteLine("Does '{0}' contain '{1}'?", s, sub1);
StringComparison comp = StringComparison.Ordinal;
Console.WriteLine(" {0:G}: {1}", comp, s.Contains(sub1, comp));
take a breakpoint a check what is column name and colID at failure moment.How are you going to solve problems, when don't even know values?
Use dc.ColumnName.ToString().StartsWith(). Contains() is good only untill string endswith.
As per this answer before : Regular expression wildcard
And here is sample of code :
private static bool Match(string pattern, string stringToCheck) {
var finalPattern = pattern.Replace("*", ".*?");
Regex regex = new Regex(finalPattern);
return regex.IsMatch(stringToCheck);
}
I have an application which stores a user selected value to the value in my dataset filled datatable. I need to set another column in the table based on this comparison. But the comparison is not working. It always returns false, not entering in the if condition.
foreach (DataRow dr in dsQuestions.Tables[0].Rows)
{
if (dr["Data"] == indicater[0])
{
dr["IsSelected"] = true;
}
}
indiactor[0] is a string array and dr["data"] is also of type string but it shows a warning that it needs to a string type.
The DataRow indexer returns the field at that index as object not as string.
I would recommend to use the strongly typed Field-extension method which also supports nullables:
if (dr.Field<String>("Data") == indicater[0]){}
... and the SetField method that also support nullable types:
dr.SetField("IsSelected", true);
Update if indicater[0] is really a string[] (not a single string), how do you want to compare a string with a string[]? If you for example want to check if the array contains this data:
if (indicater[0].Contains(dr.Field<String>("Data"))){}
That would also explain why it never enters the if: because == only compares strings by equality, other types which don't have overridden the ==-operator will compare only the reference. A string is never the same reference as a string[]. But you don't get a compile time error because you can compare an object with everything else.
First of all string can't compare using == you should use equals method:
foreach (DataRow dr in dsQuestions.Tables[0].Rows)
{
if (dr["Data"].tostring().Equals(indicater[0]))
{
dr["IsSelected"] = true;
}
May be useful for you
DataRow[] result = table.Select("Id = 1");
foreach (DataRow row in result)
{
if (row[0].Equals(indicater[0]))
{
//IsSelected
row[1]=true;
Console.WriteLine("{0}", row[0]);
}
}
Try this:
foreach (DataRow dr in dsQuestions.Tables[0].Rows)
{
if (dr["Data"].ToString() == indicater[0].ToString())
{
Convert.ToBoolean(dr["IsSelected"].ToString()) = true;
}
}
I have a datatable with data in it (customer addresses). In some instances, column ADDR3 doesn't have a value and column ADDR2 does. I'm attempting to check the value of ADDR3 and if it doesn't contain a value I want to copy the value in ADDR2 to ADDR3 and then blank out ADDR2. I was trying to use the below code, but it isn't working. I placed a breakpoint after my 'if' statement, but the program never breaks. But, I know for a fact that a lot of the rows have null ADDR3 fields. Can anyone tell me what I'm doing wrong?
foreach (DataRow row in dataSet11.DataTable1.Rows)
{
object value = row["ADDR3"];
if (value == DBNull.Value)
{
row["ADDR3"] = row["ADDR2"];
row["ADDR2"] = " ";
}
}
It's likely that your row["ADDR3"] value is NEVER equal to DbNull.Value. This is often the case with data tables that were transferred over a web service, for example (there will be empty strings instead of nulls due to the XML transformations).
Put a break point BEFORE your if and find exactly what the value is. You might try checking for row["ADDR3"] == null or string.IsNullOrWhiteSpace(row["ADDR3"].ToString())
Have you tried comparing the value to null (rather than DBNull.Value)? I believe DBNull.Value is exclusive for certain database objects and does not appear once read into a dataset/datatable.
Try this:
foreach (DataRow row in dataSet11.DataTable1.Rows)
{
if (
row.IsNull("ADDR3") // True if the value is null
||
String.IsNullOrWhitespace(row["ADDR3"]) // True if the value is "", " ", etc.
)
{
row["ADDR3"] = row["ADDR2"];
row["ADDR2"] = " ";
}
}
how can I compare value property of item to datatable column called Value? Please help me with the syntax
if ((String)item.Value.IndexOf((string)results("value") Stringcomparison.CurrentCultureIgnoreCase) > -1)
{
returnItems.Add(item);
}
Your question and comment seem to be asking different things, but in both cases the answer is probably to unpick the big long line of code into explanatory variables:
string valueFromTable = currentRow["Value"].ToString();
bool itemValueContainsValueFromTable = item.Value.IndexOf(valueFromTable, StringComparison.CurrentCultureIgnoreCase) >= 0;
bool itemValueEqualsValueFromTable = item.Value.Equals(valueFromTable, StringComparison.CurrrentCultureIgnoreCase);
if (/* whichever of these you are interested in */)
{
returnItems.Add(item);
}
(ignoring error cases here, specifically if item.Value is null)
Note that to get a value from the DataTable you will need to pick a row. If you want to see if the item matches any row, then iterate over the rows (foreach (var row in table.Rows)).