DataTable.Select string/int comparison - c#

I got crazy finding a reason with my grid don't show correctly and found that I compare a varchar column (of only numeric values) without using ' (quote).
The problem is that for some numbers the select match and for other the select don't match.
This is an example:
DataTable tab = new DataTable();
tab.Columns.Add("age", typeof(String));
DataRow row1 = tab.NewRow();
row1["age"] = "8";
tab.Rows.Add(row1);
DataRow row2 = tab.NewRow();
row2["age"] = "15";
tab.Rows.Add(row2);
Console.WriteLine("Rows with age 8="+ tab.Select("age=8").Length);
Console.WriteLine("Rows with age 15=" + tab.Select("age=15").Length);
Output is:
Rows with age 8=0
Rows with age 15=1
Why for 8 number don't match and for 15 number yes? Is this a bug?

Seems to be a bug (feature?) in internal string/int comparassing. You have to either add single quotes to compare data as strings, or if you need to compare them as integers and you're open to use LINQ you can do something like
int i = tab.AsEnumerable().Where(x => Convert.ToInt32(x["age"]) == 8).Count(); // 1

Related

How to select from a datatable where the column is part of a string in C#.net

I have a datatable that I am trying to make an update on.
my datatable is the data source of a data gridview (Forms application)
I want to update all rows that are part of a textbox
the textbox contains a comma separated values such as
A1,A11,B4,B38,C44
I have this code but stuck on how to make it working
DataTable dt = new DataTable();
dt = (DataTable)grd1.DataSource;
DataRow[] dr = dt.Select("'," + TextBox1.Text + ",' LIKE '%,Code,%'");
foreach (DataRow row in dr)
{
row["Price"] = 1000;
}
The problem is in this code
"'," + TextBox1.Text + ",' LIKE '%,Code,%'"
it does not retuen any rows so I think I did not write it the right way.
How to fix my select line?
Note : I added a comma before and after so I do not get "T37" when I am looking for "T3"
Your question wasn't easy to understand for me, but you seem to be saying that you will type a list of values into the textbox and these values are to be looked up in the [Code] column of the datatable. I'm not clear on whether the Code column itself is a single value or a comma separated list of codes, so I'll answer for both. Assuming the Code column is a CSV, and you want that any row where any one of the values in Code is one of these values in the textbox, shall have its price updated to 1000:
So for a textbox of "A1,B1" and a datarows like:
Code Price
A1,C3 200
B4,C7 400
The 200 row shall be updated and the 490 row shall not
I'd use LINQ for this rather than datatable select
var codes = textbox.Split(',');
var rows = dt.AsEnumerable().Where(r => codes.Any(c => (r["Code"] as string).Split(',').Contains(c)));
foreach(var r in rows)
r["Price") = 1000;
If you're doing this a lot I wouldn't have the codes in the row as a CSV string; a row field is allowed to be an array of strings - storing the codes as an array in the row will save having to split them every time you want to query them
If I've got this wrong and the row contains just a single Code value, the logic is the same, it just doesn't need the split(though the code above would work, it's not optimal):
var rows = dt.AsEnumerable().Where(r => codes.Any(c => (r["Code"] as string) == c));
And actually if you're going to be doing this a lot, I would index the datatable:
//if it's a csv in the datatable
var index = dt.AsEnumerable()
.SelectMany(r => r["Code"].ToString().Split(','), (row, code) => new { R=row, C=code})
.ToLookup(o => o.C, o => o.R);
This will give something like a dictionary where a code maps to a list of rows where the code appears. For a row set like
Code Price
A1,C3 200
B4,C3 400
You get a "dictionary" like:
A1: { "A1,C3", 200 }
C3: { "A1,C3", 200 },{ "B4,C3", 400 }
B4: { "B4,C3", 400 }
so you could:
foreach(var c in codesTextbox.Split)
foreach(var row in index["c"])
row["Price"] = 1000;
If the Code column doesn't contain a csv, doing a selectmany should still be fine, but to optimize it:
var index = dt.AsEnumerable().ToLookup(r => (string)r["Code"]);

Leading zeros are not adding to DataTable columns C# Asp.Net with PadLeft or String.Format

Leading zeros are not adding to DataTable columns with PadLeft or String.Format.
Initially I copied user uploaded excel data to Datatable. I'm trying to add zeros in front of datatable column values if the length is less than 8 digits and after that I have to compare with another table for matching records. If I don't have leading zeros I'm missing those records while matching with other datatable columns. But I want them to be with leading zeros so they can be matched to get correct results.
Ex: I have a column "CODE" in datatable with values 30500, 501080, 5020900, 19010300 etc and Would like to have my results like 00030500, 00501080, 05020900, 19010300
Note: I would like the change the data in the Datatable not in the sql query which retrieves the data. I don't want code for converting int to string leading zeros. Even I tried in that way didn't fix my issue.
I tried couple of ways but it didn't solve. What's wrong with my code. It's not working. I used below from How to add leading zeros in DataTable columns but still not changed anything. Don't consider this post as duplicate, As I tried all ways but still the problem exist, Hence posting here.
Approach 1:
foreach (DataRow row in dataExcelInputTable.Rows)
{
row["CODE"] = row["CODE"].ToString().PadLeft(8, '0');
}
dataExcelInputTable.AcceptChanges();
Approach 2:
foreach (DataRow drin dataExcelInputTable.Rows)
{
dr["CODE"] = String.Format("{0:00000000}", int.Parse(dr["CODE"].ToString()));
}
dataExcelInputTable.AcceptChanges();
Approach 3:
int countSize = 0;
int val = 0;
foreach (DataRow row in dataExcelInputTable.Rows)
{
countSize = row["CODE"].ToString().Length;
val = int.Parse(row["CODE"].ToString());
if (countSize < 8)
{
row["CODE"] = val.ToString("D8");
//Response.Write("<br/>" + val.ToString("D8"));
}
}
dataExcelInputTable.AcceptChanges();
Update:
foreach (DataRow row in dataExcelInputTable.Rows)
{
if (row["CODE"].ToString().Length < 8)
{
row["CODE"] = row["CODE"].ToString().PadLeft(8, '0');
}
Response.Write("<br/>" + row["CODE"]);
}
dataExcelInputTable.AcceptChanges();
Right now its printing below, its not padding zero front.
9040100 (<8) , 9070100 (<8) , 9090200 (<8) , 9090300 (<8)
10020300 (=8) , 10030300 (=8) , 11010100 (=8)
I tried at my end and getting expected output ... below is a test code
System.Data.DataTable dt = new System.Data.DataTable();
dt.Columns.Add("Code");
System.Data.DataRow r = dt.NewRow();
r["Code"] = "30500";
dt.Rows.Add(r);
foreach (System.Data.DataRow row in dt.Rows)
{
row["CODE"] = row["CODE"].ToString().PadLeft(8, '0');
}
dt.AcceptChanges();
//dt.Rows[0][0] value is 00030500
Finally got one solution, Thank you Sami for your idea about Datatype . I found that Datatype is double but I need string to make padding left zeros using PadLeft method.
As I can't change Datatype of a Datatable after filling data from excel sheet. I cloned to a new Datatable and then changed it's datatype to string. Below is the sample code.
dtCloned = dataExcelInputTable.Clone();
dtCloned.Columns[1].DataType = typeof(System.String); // clone the datatble and make a column datatype to string
foreach (DataRow row in dataExcelInputTable.Rows)
{
dtCloned.ImportRow(row);
}
foreach (System.Data.DataRow row in dtCloned.Rows)
{
row["CODE"] = row["CODE"].ToString().PadLeft(8, '0');
Response.Write("<br/>" + row["CODE"]);
}
dtCloned.AcceptChanges();
This is working as expected. But I was looking for any direct simple way other than clone? I tried below from https://msdn.microsoft.com/en-us/library/dd260048%28v=vs.110%29.aspx but it's not working. Giving me an error "No overload for method 'ToString' takes 1 arguments".
string fmt = "00000000.##";
foreach (System.Data.DataRow row in dataExcelInputTable.Rows)
{
row["CODE"] = row["CODE"].ToString(fmt);
Response.Write("<br/>" + row["CODE"]);
}

DataTable - how to achieve datacolumn expression (computed column) persistence?

I have this DataTable
FName LName Tag1 Tag2 Tag3 ... (not fixed, can be as many)
What I want is
FName LName TagAll
So, I created a column TagAll of type string with expression as
var expression = string.Empty;
// ... other code
// In a loop for all tag columns
expression = expression + " + ',' + " + tagColumn;
// at the end of loop
dtContact.Columns["Tag_All"].Expression = expression;
So, if I have 3 columns, the expression is like this
"Tag1 + ',' + Tag2 + ',' + Tag3"
For example the data is
FName LName Tag1 Tag2 Tag3
Jeff Atwood test tag other
Matt breeden myTag total last
The resulting DataTable becomes like this
FName LName Tag1 Tag2 Tag3 Tag_All
Jeff Atwood test tag other test, tag, other
Matt breeden myTag total last myTag, total, last
It is fine till now, but now I would like to remove all these other Tag(s) column. I tried doing
dtContact.Columns.RemoveAt(2) but it throws 'System.ArgumentException'
I am guessing it is because that column is used in a computed column expression, is that correct? Because when I remove column 0 or column 1. It works fine. So, is there a way that I could remove all these other Tag(s) column, given that they are used in a computed column expression? May be somehow make this column persistent? Though I searched for it on Google but couldn't find anything.
Also, like I said, it is not fixed that there would only be 2, or 3 or n number of these Tag(s) column, they are dynamic, and there can be just 1, Tag1, upto any... say Tag88 or whatever.
Try this method:
//Usage
DataTable dtMod = GetModifiedTable( dt);
//Function to return modified data table
public DataTable GetModifiedTable(DataTable dt)
{
var columnList = dt.Columns.Cast<DataColumn>()
.Where(x => x.ColumnName.StartsWith("Tag"))
.Select(x => x.ColumnName)
.ToArray();
DataTable dtNew = new DataTable();
dtNew.Columns.Add("FName");
dtNew.Columns.Add("LName");
dtNew.Columns.Add("Tag_All");
var results = dt.AsEnumerable().Select(r =>
dtNew.LoadDataRow(
new object[] {
r.Field<string>("FName"),
r.Field<string>("LName"),
GetTagValues(r, columnList)
}, false
));
dtNew.Rows.Add(results.ToArray());
return dtNew;
}
//Function to return csv values of given column list
public string GetTagValues(DataRow r, string[] columns )
{
string csv = string.Empty;
foreach(string column in columns)
{
csv += r[column].ToString() + ",";
}
return csv.Substring(0, csv.Length - 1);
}
You can't do this. You have to take another approach.
Add the TAG_ALL column but not as a computed column. For each row in the DataTable, go through all the TagX columns adding them up, and then assign the value to the Tag_All column. Repeat for each row. When finished, you can now delete the TagX columns.
Depending on the number of rows, this can actually be quite fast.
However, I'd question whether this is a good idea. If you are databinding the DataTable to some grid, then all you need do it not bind the TagX columns, or tell the Grid to make those columns invisible.
While handling huge data inside a datatable (about 500000 rows), looping over rows is taking time (even with dt.AsEnumerable().Select() method). I was searching for a faster method until i found the following workaround:
Clone the datatable (only structure) to a new table
Loop over columns and remove expression (set to ""), or just remove the expression for a specific Datacolumn
Merge the new datatable with the old one.
Now you can delete the original column without affecting the computed column.
Example:
//assign expression
var expression = string.Empty;
expression = expression + " + ',' + " + tagColumn;
dtContact.Columns["Tag_All"].Expression = expression;
//Clone datatable structure
DataTable dtNew = dtContact.Clone();
//Remove expression from a specific column
dtNew.Columns["Tag_All"].Expression = "";
//Merge data with the new Table
dtNew.Merge(dtContact);
dtContact.Dispose();
//Now you can remove the column used within the expression
dtNew.Columns.RemoveAt(2);
Check out this code:
private void creatable()
{
dt.Columns.Add("FName");
dt.Columns.Add("LName");
dt.Columns.Add("Tag1");
dt.Columns.Add("Tag2");
dt.Columns.Add("Tag3");
dt.Columns.Add("Tag_All");
}
private void removeColumn()
{
string temp = null;
List<string> colToRemove = new List<string>();
int colcount = dt.Columns.Count;
for (int i = 0; i <colcount ;i++ )
{
temp = dt.Columns[i].ColumnName;
if (temp == "Tag1" || temp == "Tag2" || temp == "Tag3")
{
colToRemove.Add(temp);
}
temp = null;
}
foreach (string item in colToRemove)
{
dt.Columns.Remove(item);
}
}
It's working as per your requirements.

How can I delete and rewrite rows in a datatable and write them back from a datarow[] array in a different order?

I tried saving the datatable rows to a datarow[] array then deleting the datatable rows and rewrite them to the datatable from the datarow[] array in a different order. But when I delete them from the datatable I can't access them from the datarow[] array.
I don't know if I'm doing this correct or if im totally off base but I'm in desperate need of help.
This is what I'm trying to do:
I have a datatable with 8 rows.
I want to be able to somehow loop thru the 8 rows and reorder them based on certain criterias.
For example, my rows have an Invoice number, line number, and Part number as the key fields. Depending on the criteria, I may need rows 6,7,8 to be in the beginning as rows 1,2,3 and shift the rest down.
If anyone has an Idea please reply....this is an urgent issue.
thank you,
Sam
Use the DataView to view the rows because it can be sorted like this.
dataTable.DefaultView.Sort = "columnNameA, columnNameB desc";
Then bind the list, whatever you're using, to the DataView.
Oh and here's some documentation on the Sort property.
If you take a DataRow:
var dr = table.Rows[0];
and then delete the row from the table, dr will also be deleted. It's the same row. That explains your problem.
As for the solution, don't reorder rows in a DataTable. Instead, add another column called RowOrder, modify it to change the order, and then sort the DataTable based on that column, as #mperrenoud03 has shown you:
table.DefaultView.Sort = "RowOrder";
and use the default view.
You can try with something like this:
DataTable table = new DataTable();
table.Columns.Add("Invoice", typeof(string));
table.Columns.Add("Line", typeof(int));
table.Columns.Add("Part", typeof(string));
for (int i = 1; i <= 9; i++)
table.Rows.Add("Invoice " + i, i, "Part " + i);
//reorder them:
int[] orders = { 6, 7, 8, 1, 2, 3, 4, 5 };
DataTable table2 = table.Clone();
DataRow row;
for (int i = 0; i < orders.Length; i++)
{
row = table2.NewRow();
row["Invoice"] = table.Rows[orders[i]]["Invoice"];
row["Line"] = table.Rows[orders[i]]["Line"];
row["Part"] = table.Rows[orders[i]]["Part"];
table2.Rows.Add(row);
}

Using Split and storing in a data table

I am looking at how to split a string and store the info in a datatable. I can get the split and store to work correctly but the issue comes in how I am trying to use the split. this is an example of the string I have:
itemid/n3,itemid/n4
itemid is the items unique id and after /n is how many of the item the user has selected, the comma seperates the entries
I have a data table like this:
DataTable table = new DataTable();
table.Columns.Add("id", typeof(int));
table.Columns.Add("count", typeof(int));
Id like to be able to split the string at the comma and then store each of the values in the data table so they appear on the same row (split at the /n) is there an easy way to do this using split? or am I better off doing it another way
Yeah, you may split by the comma first and by /n afterwards:
foreach(var row in myString.Split(','))
{
var fields = row.Split(new string[] { "/n" },
StringSplitOptions.None);
// fields[0] is ID, fields[1] is count
}
This still executes in linear time, therefore it may definitely be a way to go.
If "/n" and "," are always present for each record, you can use a regular expression split with the expression "(?:/n|\,)" and then loop with x+=2 instead of x++ through the list. X will be the ID, X+1 will be the value.
string Input = "12/nTwelve,13/nThirteen,";
string[] InputSplit = Regex.Split(Input, #"(?:/n|\,)");
for(int i = 0 ; i < ((InputSplit.Length / 2) * 2) ; i+=2){
//Math in the middle helps when there's a trailing comma in the data set
Console.WriteLine(string.Format("{0}\t{1}", InputSplit[i], InputSplit[i+1]));
}
Note that for the example, I changed the type of the first column, as in the provided sample string, id is a string.
DataTable table = new DataTable();
table.Columns.Add("id", typeof(string));
table.Columns.Add("count", typeof(int));
var str = "itemid/n3,itemid/n4";
var items =
str.Split(',').Select(
r =>
new
{
Id = r.Split(new[] {"/n"}, StringSplitOptions.RemoveEmptyEntries).First(),
Count = int.Parse(r.Split(new[] {"/n"}, StringSplitOptions.RemoveEmptyEntries).Last())
});
foreach (var item in items)
{
var row = table.NewRow();
row["id"] = item.Id;
row["count"] = item.Count;
}

Categories