C# DataTable -> Need to generate an ID based on the column values - c#

Value
ID
A
A
A
B
B
C
Desired output
Value
ID
A
1
A
1
A
1
B
2
B
2
C
3
I need to create IDs based on grouping the value column. A single ID for all A's and then B's respectively.
Thanks in advance!

You could simply use a loop like:
for(int i = 0; i < dataTable.Rows.Count; i++){
switch(dataTable.Rows[i][0].ToString()){
case "A" :
dataTable.Rows[i][1] = 1;
break;
case "B" :
dataTable.Rows[i][1] = 2;
break;
case "C" :
dataTable.Rows[i][1] = 3;
break;
// other cases
}
}

here dt is your datatable. Use a loop like this:
int id = 1;
for (int i = 0; i < dt.Rows.Count; i++)
{
if (i == 0)
{
dt.Rows[i]["ID"] = id;
if (i != dt.Rows.Count && dt.Rows[i + 1]["Value"] != dt.Rows[i]["Value"])
{
id++;
}
}
else
{
if (dt.Rows[i - 1]["Value"] == dt.Rows[i]["Value"])
{
dt.Rows[i]["ID"] = id;
}
else
{
id = id + 1;
dt.Rows[i]["ID"] = id;
}
}
}

If the values might fall between A-Z then consider the following done in a form but can be done where ever you want.
public class Replacement
{
public string Value { get; set; }
public int Index { get; set; }
}
Form code
private void SetIdButton_Click(object sender, EventArgs e)
{
var dataTable = MockedDataTable();
var items = ReplacementData;
for (int index = 0; index < dataTable.Rows.Count; index++)
{
dataTable.Rows[index].SetField("ID",
items.FirstOrDefault(replacement =>
replacement.Value == dataTable.Rows[index].Field<string>("Value")).Index);
}
foreach (DataRow row in dataTable.Rows)
{
Console.WriteLine($"{string.Join(",", row.ItemArray)}");
}
}
private static Replacement[] ReplacementData
=> "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
.ToCharArray().Select((value, index) => new Replacement
{
Value = value.ToString(),
Index = index + 1
})
.ToArray();
private static DataTable MockedDataTable()
{
DataTable dataTable = new DataTable();
dataTable.Columns.Add("Value", typeof(string));
dataTable.Columns.Add("ID", typeof(int));
dataTable.Rows.Add("A");
dataTable.Rows.Add("A");
dataTable.Rows.Add("A");
dataTable.Rows.Add("B");
dataTable.Rows.Add("B");
dataTable.Rows.Add("C");
dataTable.Rows.Add("D");
return dataTable;
}
Output
A,1
A,1
A,1
B,2
B,2
C,3
D,4

You could also use a dictionary:
dictionary<string,int> record_dic = new dictionary<string,int>{{"A",1},{"B",2}};

Related

Simplest way to load data to DataGridView from parent and child table with possibility of sorting

I working with C# and EF 6.4 and I am looking for clean and easy solution for my problem.
I have simple base like that:
and I want to load data to DataGridView to be looked like that:
I have tried the following solutions:
1
//sorting working, but doesn`t show columns "name" and "title"
Db.BooksAuthors.Include(x => x.Authors).Load();
DataGridView1.DataSource = Db.BooksAuthors.Local.ToBindingList;
2
//sorting not working, but shows columns "name" and "title"
public class BooksAuthorsView
{
public Int32 idBook { get; set; }
public Int32 idAuthor { get; set; }
public string BookTitle { get; set; }
public string AuthorName { get; set; }
}
private void Show()
{
var list = (from BA in Db.BooksAuthors
select new BooksAuthorsView() { idBook = BA.idBook, idAuthor = BA.idAuthor, BookTitle = BA.Books.title, AuthorName = BA.Authors.name });
DataGridView1.DataSource = new BindingSource() { DataSource = new BindingList<BooksAuthorsView>(list.ToList) };
}
EDIT:
I checked this solution. It`s working, but is it the simplest solution?
If you want to sort by clicking on the column header, you can refer to the following code:
public DataTable dvtodt(DataGridView dv)
{
DataTable dt = new DataTable();
DataColumn dc;
for (int i = 0; i < dv.Columns.Count; i++)
{
dc = new DataColumn();
dc.ColumnName = dv.Columns[i].HeaderText.ToString();
dt.Columns.Add(dc);
}
for (int j = 0; j < dv.Rows.Count; j++)
{
DataRow dr = dt.NewRow();
for (int x = 0; x < dv.Columns.Count; x++)
{
dr[x] = dv.Rows[j].Cells[x].Value;
}
dt.Rows.Add(dr);
}
return dt;
}
private int SortOrder_ = 0;
private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
int nColumnIndex = e.ColumnIndex;
DataTable d = dvtodt(dataGridView1);
if (SortOrder_ == 0)
{
d.DefaultView.Sort = this.dataGridView1.Columns[nColumnIndex].Name + " ASC";
SortOrder_ = 1;
}
else if (SortOrder_ == 1)
{
d.DefaultView.Sort = this.dataGridView1.Columns[nColumnIndex].Name + " desc";
SortOrder_ = 0;
}
dataGridView1.Columns.Clear();
dataGridView1.DataSource = d;
}

Sort rows of DataTable in particular order we want

Consider I have DataTable with two columns
Column1
Column2
abc
jan
def
dec
cba
feb
bdf
nov
aaa
dec
I have list of months for column2.
I want to sort it based on months
Output:
Column1
Column2
def
dec
aaa
dec
bdf
nov
cba
feb
abc
jan
What I tried:
string[] monthList = { "dec", "nov", "oct", "sep",,,, "feb", "jan"};
DataTable dataExistingTable
DataTable temp = new DataTable();
foreach(string month in monthList)
{
var filteredData = dataExistingTable.AsEnumerable().Where(x => x["Column2"] == month);
temp.Merge(filteredData.CopyToDataTable());
}
Please suggest some faster/better way!!!
Add an expression based column
dataExistingTable.Columns.Add("MonthNumber", typeof(int),
"IIF ([Column2] = 'dec', 12, (IIF ([Column2] = 'nov', 11, (IIF ([Column2] = 'oct', 10, (IIF ([Column2] = 'sep', 9, 8)))))))");
Then apply sort on expression based column
dataExistingTable.DefaultView.Sort = "MonthNumber desc";
Then DefaultView of the table will give your sorted rows.
// dataExistingTable.DefaultView;
In my applications, I am usually using this algorithm to sort different items in a special order. Only OrderedListItem usualy is a DB table, and Column2 usually is a foreign key of OrderedListItem. This way I don't need the second query.
public static void Main()
{
List<OrderedListItem> orderedList=null;
List<TableItem> tableItems=null;
PrepareData( ref orderedList, ref tableItems);
var query =
(from ti in tableItems
join ol in orderedList on ti.Column2 equals ol.Name
select new
{
Column1 = ti.Column1,
Column2 = ti.Column2,
OrderNum = ol.OrderNum
}).OrderBy(i => i.OrderNum)
.ToArray();
var result = query.Select(q => new TableItem { Column1 = q.Column1, Column2 = q.Column2 }).ToArray();
}
public static void PrepareData(ref List<OrderedListItem> orderedList, ref List<TableItem> tableItems)
{
orderedList = new List<OrderedListItem> {
new OrderedListItem ( 1,"Jan",100),
new OrderedListItem ( 2,"Feb",200),
new OrderedListItem ( 3,"Mar",300),
new OrderedListItem ( 4, "Apr",400)
};
tableItems = new List<TableItem> {
new TableItem {Column1="cba", Column2="Feb"},
new TableItem {Column1="abc", Column2="Jan"},
};
}
public class OrderedListItem
{
public int Id { get; set; }
public string Name { get; set; }
public int OrderNum { get; set; }
public OrderedListItem(int id, string name, int orderNum)
{
Id=id;
Name=name;
OrderNum=orderNum;
}
}
public class TableItem
{
public string Column1 { get; set; }
public string Column2 { get; set; }
}
Waqas Raja's answer helped me in solving problem and writing this answer.
The easier/faster way to sort in the order you want is using DataColumn's Expression property.
you can assign expression to column in ways
table.Columns.Add(columnName, type, expression);
or
table.Columns.Add(columnName, type);
table.Columns[columnName].Expression = expression;
for this question I used IIF in expression which works like If Else statement
usage: IIF(condition, 'true statement', 'false statement')
To know what kind of conditions can be used go through https://learn.microsoft.com/en-us/dotnet/api/system.data.datacolumn.expression?view=net-5.0
Below method gives expression for list having any number of objects
private static string GetColumnExpression(List<object> distinctList, string columnName)
{
int counter = 1;
string expression = "";
foreach (var item in distinctList)
{
expression += $"IIF ([{columnName}] = '{item}', {counter++}, ";
}
expression += "0";
for (int k = 0; k < distinctList.Count; k++)
{
expression += ")";
}
return expression;
}
When you apply the expression of having conditions(like IIF) more than 90/96 it might throw exception saying "Expression is too complex"
For the list having objects more than 90/96 below code will help!
private static List<string> GetColumnExpressions(List<string> distinctList, string columnName, string sortingColumn)
{
var list = distinctList;
List<string> expressions = new List<string>();
int maxLimit = 90, listCount = distinctList.Count, counter = 1, loopCount = 1;
while (listCount != 0)
{
string expression = "";
if (loopCount > 1)
{
expression = $"IIF ([ExtraCol] <> 0, [ExtraCol], ";
}
int conditionCount = listCount >= maxLimit ? maxLimit : listCount;
for (int i = 0; i < conditionCount; i++)
{
expression += $"IIF ([{columnName}] = '{list[0]}', {counter++}, ";
list.RemoveAt(0);
}
expression += "0";
for (int k = 0; k < conditionCount; k++)
{
expression += ")";
}
if (loopCount > 1)
{
expression += ")";
}
expressions.Add(expression);
listCount = list.Count;
loopCount++;
}
return expressions;
}
And usage like:
//"MonthNumber" is column Added For Expression/Sorting
var expressions = GetColumnExpressions(list, "Column2", "MonthNumber");
table.Columns.Add("MonthNumber", typeof(int));
string sourceColumn = "MonthNumber", destColumn = "MonthNumber";
for (int k = 0; k < expressions.Count; k++)
{
if (k > 0)
{
destColumn = "Extra" + k;
table.Columns.Add(destColumn, typeof(int));
for (int row = 0; row < table.Rows.Count; row++)
{
table.Rows[row][destColumn] = table.Rows[row][sourceColumn];
}
expressions[k] = expressions[k].Replace("ExtraCol", sourceColumn);
sourceColumn = destColumn;
}
string expression = expressions[k];
table.Columns[destColumn].Expression = expression;
}
Sort And Delete Extra Columns
table.DefaultView.Sort = $"{destColumn}";
for (int k = expressions.Count - 1; k > 0; k--)
{
table.Columns.Remove("Extra" + k);
}
table.Columns.Remove("MonthNumber");
var sortedTable = table.defaultView.ToTable();

How to sort C# Datatable with empty column values at the end

I have a C# datatable with 1000's of rows. But primary 200 rows have empty values (multiple columns). Filter would happen to those columns as empty values to occupy as last. I want output will happen with in the table or new table with filter but not as linq rows. Please help me out
Pictures speaks more words, refer this for better understanding:
There are 2 things here that you should consider:
"Empty Value": To sort items that followed by empty value columns you need .OrderBy(s => String.IsNullOrEmpty(s["OrderNo"].ToString()))
"Order No Format": To sort custom order number you need to use IComparer<string>. As you show in your question I assume the format for Order No could be XXXXX or XXXX-XXXX.
So you need to first OrderBy empty values ThenBy your custom IComparer<string> something like this:
public class OrderNoComparer : IComparer<string>
{
public int Compare(string x, string y)
{
int xFirstValue = 0;
int xSecondValue = 0;
int yFirstValue = 0;
int ySecondValue = 0;
if (x.Contains("-"))
{
xFirstValue = Convert.ToInt32(x.Split(new char[] { '-' })[0]);
xSecondValue = Convert.ToInt32(x.Split(new char[] { '-' })[1]);
}
else
{
xFirstValue = Convert.ToInt32(x);
}
if (y.Contains("-"))
{
yFirstValue = Convert.ToInt32(y.Split(new char[] { '-' })[0]);
ySecondValue = Convert.ToInt32(y.Split(new char[] { '-' })[1]);
}
else
{
yFirstValue = Convert.ToInt32(y);
}
if (xFirstValue > yFirstValue)
{
return 1;
}
else if (xFirstValue < yFirstValue)
{
return -1;
}
else //means equal
{
if (xSecondValue > ySecondValue)
{
return 1;
}
else if (xSecondValue == ySecondValue)
{
return 0;
}
else
{
return -1;
}
}
}
}
Full example is here:
DataTable dtOrder = new DataTable();
dtOrder.Columns.Add("OrderNo");
var dr1 = dtOrder.NewRow();
dr1["OrderNo"] = "";
dtOrder.Rows.Add(dr1);
var dr2 = dtOrder.NewRow();
dr2["OrderNo"] = "569-875";
dtOrder.Rows.Add(dr2);
var dr3 = dtOrder.NewRow();
dr3["OrderNo"] = "569975";
dtOrder.Rows.Add(dr3);
var dr4 = dtOrder.NewRow();
dr4["OrderNo"] = "569865";
dtOrder.Rows.Add(dr4);
var dr5 = dtOrder.NewRow();
dr5["OrderNo"] = "569-975";
dtOrder.Rows.Add(dr5);
var dr6 = dtOrder.NewRow();
dr6["OrderNo"] = "569-875";
dtOrder.Rows.Add(dr6);
var result = dtOrder.AsEnumerable().AsQueryable()
.OrderBy(s => String.IsNullOrEmpty(s["OrderNo"].ToString()))
.ThenBy(o => o["OrderNo"].ToString(), new OrderNoComparer())
.ToList();
foreach (var item in result)
{
Console.WriteLine(item["OrderNo"]);
}
Then the output would be like you expected:
569-875
569-875
569-975
569865
569975

Generic hierarchical DataView class with dynamic levels of sort keys

When we create a DataView with sort on multiple columns, we are forced to specify values of all the columns when using FindRows.
Example, if I have:
dv1 = new DataView(tbl1, null, "Col1, Col2, Col3", DataViewRowState.CurrentRows);
I am forced to specify values for all 3 columns when using FindRows.
It is not possible to retrieve all rows for a particular value of Col1 and "any" value for Col2 and Col3.
This is possible in a SortedList where I can have nested SortedList. So, the top level SortedList will have key as Col1, and value as another SortedList. The 2nd level SortedList will have key as Col2, and value as another SortedList. Finally, the 3rd level SortedList will have key as Col3, and the references to the DataRows.
Using such a SortedList, it is possible to write methods, like:
public DataRowView[] FindAny(object[] keys)
where if the keys array contains only 1 key, the code can find the 2nd level SortedList from the 1st level SortedList for the key, and then loop through the 2nd and 3rd level SortedList and return all rows, as they belong to 1st key.
My question is whether anyone has already written such a SortedList class which can take dynamic number of keys, and work with any DataTable / DataRow.
Note:
1. This question has nothing to do with presentation layer. I am looking for a helper class for data processing, say for analysing huge volume of data in multiple excel sheets.
2. I am not looking for a LINQ based solution currently. I will migrate to LINQ in future.
Thanks.
I managed to write my own generic class, which implements a hierarchial view of the data. Improvements in this are welcome.
public class HDataView
{
private SortedList mapDataRows;
int numMapLevels;
public HDataView(DataTable tbl, string[] colNames)
{
object colVal = null;
string colName = "";
SortedList currLvlMap = null, nextLvlMap = null;
DataRow dr1 = null;
ArrayList arlLastLvlData;
mapDataRows = new SortedList();
numMapLevels = colNames.Length;
for (int i = 0; i < tbl.Rows.Count; i++)
{
dr1 = tbl.Rows[i];
currLvlMap = mapDataRows;
for (int j = 0; j < colNames.Length; j++)
{
colName = colNames[j];
colVal = dr1[colName];
if (j == colNames.Length - 1)
{
arlLastLvlData = (ArrayList)currLvlMap[colVal];
if (arlLastLvlData == null)
{
arlLastLvlData = new ArrayList();
currLvlMap[colVal] = arlLastLvlData;
}
arlLastLvlData.Add(dr1);
}
else
{
nextLvlMap = (SortedList)currLvlMap[colVal];
if (nextLvlMap == null)
{
nextLvlMap = new SortedList();
currLvlMap[colVal] = nextLvlMap;
}
//For the next column, the current nextLvlMap will become the prevLvlMap
currLvlMap = nextLvlMap;
}
}
}
}
public ArrayList FindAnyRows(object[] keys)
{
object keyVal = "";
ArrayList arlDataRows = null, arlCurrRows = null;
SortedList startLvlMap = null, currLvlMap = null, nextLvlMap = null;
int mapLevel = 1, startLevel = 0;
currLvlMap = mapDataRows;
mapLevel = 1;
for (int i = 0; i < keys.Length; i++)
{
keyVal = keys[i];
if (currLvlMap == null)
{
break;
}
if (i == numMapLevels - 1)
{
arlDataRows = (ArrayList)currLvlMap[keyVal];
}
else
{
nextLvlMap = (SortedList)currLvlMap[keyVal];
currLvlMap = nextLvlMap;
mapLevel++;
}
}
if (arlDataRows == null)
{
arlDataRows = new ArrayList();
}
if (keys.Length > 0 && keys.Length < numMapLevels)
{
if (currLvlMap != null)
{
startLvlMap = currLvlMap;
startLevel = mapLevel;
if (mapLevel == numMapLevels)
{
for (int j = 0; j < startLvlMap.Count; j++)
{
arlCurrRows = (ArrayList)startLvlMap.GetByIndex(j);
if (arlCurrRows != null)
{
arlDataRows.AddRange(arlCurrRows);
}
}
}
else
{
for (int i = 0; i < startLvlMap.Count; i++)
{
mapLevel = startLevel;
currLvlMap = startLvlMap;
//travel full depth of this map, for each element of this map
for (; mapLevel <= numMapLevels; mapLevel++)
{
if (mapLevel == numMapLevels)
{
for (int j = 0; j < currLvlMap.Count; j++)
{
arlCurrRows = (ArrayList)currLvlMap.GetByIndex(j);
if (arlCurrRows != null)
{
arlDataRows.AddRange(arlCurrRows);
}
}
}
else
{
//Next level of current element of the starting map
nextLvlMap = (SortedList)currLvlMap.GetByIndex(i);
currLvlMap = nextLvlMap;
}
}
}
}
}
}
return arlDataRows;
}
}

Add a datarow to a datatable based on the previous rows computation at runtime

I have the below datatable(kindly note that the columns numbers will not be known at compile time)
DataTable dt = new DataTable();
dt.Columns.Add("Summary");
dt.Columns.Add("Beta");
dt.Columns.Add("Delta");
dt.Rows.Add("Summary1", "n/a", "1");
dt.Rows.Add("Summary2", "1.00", "2");
Now to this datatable , I have to add 1 more row that will be the subtraction of
dt.Rows[0].Columns[i+1] - dt.Rows[1].Columns[i+1]
where i=0
so the final datatable will be
Summary Beta Delta
---------------------------------
Summary1 n/a 1
Summary2 1.00 2
Summary3 n/a -1
I am very new to dotnet. Please help
i'm not sure what you mean but by your final table i could think of this:
DataRow summary1 = dt.Rows[0], summary2 = dt.Rows[1], summary3 = new DataRow();
summary3[0] = "Summary3";
for(int i=1; i < summary1.Table.Columns.Count; i++)
{
try{
summary3[i] = double.Parse(summary1[i].ToString()) - double.Parse(summary2[i].ToString())
}catch{
summary3[i] = "n/a";
}
}
This code allows you to have a variable amount of DataColumn inside the DataRow
I'm not sure what you want either, but I think you are having problems with the strings in the columns and converting them to ints and back. Here's an example of code that does the conversion:
private string CalculateColumnTotal(int row, int column)
{
int column1Value;
bool parsed = int.TryParse(_table.Rows[row][column].ToString(), out column1Value);
if (!parsed) return "n/a";
int column2Value;
parsed = int.TryParse(_table.Rows[row + 1][column].ToString(), out column2Value);
if (!parsed) return "n/a";
var total = column1Value - column2Value;
return total.ToString();
}
}
The full form code looks something like this:
public partial class Form1 : Form
{
private readonly DataTable _table = new DataTable("Table");
public Form1()
{
InitializeComponent();
_table.Columns.Add("Summary");
_table.Columns.Add("Beta");
_table.Columns.Add("Delta");
const int rowPairs = 1;
for (int i = 0; i <= rowPairs - 1; i++)
{
_table.Rows.Add("Summary1", "n/a", 1);
_table.Rows.Add("Summary2", 1.00, 2);
_table.Rows.Add("Summary3", null, null);
}
for (int row = 0; row < _table.Rows.Count - 1; row += 3)
{
string columnOneTotal = CalculateColumnTotal(row, 1);
string columnTwoTotal = CalculateColumnTotal(row, 2);
_table.Rows[row + 2][1] = columnOneTotal;
_table.Rows[row + 2][2] = columnTwoTotal;
}
dataGridView1.DataSource = _table;
}
private string CalculateColumnTotal(int row, int column)
{
int column1Value;
bool parsed = int.TryParse(_table.Rows[row][column].ToString(), out column1Value);
if (!parsed) return "n/a";
int column2Value;
parsed = int.TryParse(_table.Rows[row + 1][column].ToString(), out column2Value);
if (!parsed) return "n/a";
var total = column1Value - column2Value;
return total.ToString();
}
}

Categories

Resources