Sorting DataTable-columns - c#

I have a datatable something like this:
| Col1 | Col6 | Col3 | Col43 | Col0 |
---------------------------------------------------
RowA | 1 | 6 | 54 | 4 | 123 |
As you see, the Cols are not sorted by their numbers. That is what I want it to look like after the "magic":
| Col0 | Col1 | Col3 | Col6 | Col43 |
---------------------------------------------------
RowA | 123 | 1 | 54 | 6 | 4 |
Is there a built-in function for such things in C#? And if not, how could I get started with this?

You can do the column sorting in the table itself:
dt.Columns["Col0"].SetOrdinal(0);
dt.Columns["Col1"].SetOrdinal(1);
dt.Columns["Col2"].SetOrdinal(2);

You don't need to sort the columns in the DataTable object, just copy the column names to an array and sort the array. Then use the array to access the column values in the right order.
Sample:
class Program
{
static void Main(string[] args)
{
var dt = new DataTable { Columns = { "A3", "A2", "B1", "B3", "B2", "A1" } };
dt.BeginLoadData();
dt.Rows.Add("A3val", "A2val", "B1val", "B3val", "B2val", "A1val");
dt.EndLoadData();
string[] names=new string[dt.Columns.Count];
for (int i = 0; i < dt.Columns.Count;i++ )
{
names[i] = dt.Columns[i].ColumnName;
}
Array.Sort(names);
foreach (var name in names)
{
Console.Out.WriteLine("{0}={1}", name, dt.Rows[0][name]);
}
Console.ReadLine();
}

Here is my code, surely not is the best solution but works. In my case I let a fixed column that could be "Nombre" or "Problem", this always be the first in column order.
// class
public class stringInt
{
public string Nombre;
public int orden;
}
// function
static public DataTable AlphabeticDataTableColumnSort(DataTable dtTable)
{
//vamos a extraer todos los nombres de columnas, meterlos en una lista y ordenarlo
int orden = 1;
List<stringInt> listaColumnas = new List<stringInt>();
foreach (DataColumn dc in dtTable.Columns)
{
stringInt columna = new stringInt();
columna.Nombre = dc.Caption;
if ((dc.Caption != "Problema") && (dc.Caption != "Nombre")) columna.orden = 1;
else columna.orden = 0;
listaColumnas.Insert(0,columna);
}
listaColumnas.Sort(delegate(stringInt si1, stringInt si2) { return si1.Nombre.CompareTo(si2.Nombre); });
// ahora lo tenemos ordenado por nombre de columna
foreach (stringInt si in listaColumnas)
{
// si el orden es igual a 1 vamos incrementando
if (si.orden != 0)
{
si.orden = orden;
orden++;
}
}
listaColumnas.Sort(delegate(stringInt si1, stringInt si2) { return si1.orden.CompareTo(si2.orden); });
// tenemos la matriz con el orden de las columnas, ahora vamos a trasladarlo al datatable
foreach(stringInt si in listaColumnas)
dtTable.Columns[si.Nombre].SetOrdinal(si.orden);
return dtTable;
}

var columnArray = new DataColumn[table.Columns.Count];
table.Columns.CopyTo(columnArray, 0);
var ordinal = -1;
foreach (var orderedColumn in columnArray.OrderBy(c => c.ColumnName))
orderedColumn.SetOrdinal(++ordinal);

You'll probably need to implement your IComparer<T>, as "natural" order would be: Col0, Col1, Col3, Col43 and Col6. ("4" comes before "6")

Here's a combination of the preceding answers. Use the built in Sort() method of a List or an Array of strings to sort a list of the column names, then use the DataColumn.SetOrdinal() method to rearrange your DataTable's columns to match the sorted list.
List<string> columnNames = new List<string>();
foreach (DataColumn col in table.Columns)
{
columnNames.Add(col.ColumnName);
}
columnNames.Sort();
int i = 0;
foreach (string name in columnNames)
{
table.Columns[name].SetOrdinal(i);
i++;
}
(Where "table" is the name of your DataTable.)

Related

Multiple lines and columns in Specflow

I have a Specflow table that looks like this.
When I Perform POST Operation for "/example/" with body
| answerValue1 | answerValue2 | countryCode | Cash |
| Yes | Yes | AD | 76-100% |
| | | AF | |
The column CountryCode is the only one that can be multiple choices.
What I tried to do was to add the columns to dictionary with a simple tableExtenstions
public class TableExtensions
{
public static Dictionary<string, string> ToDictionary(Table table)
{
var dictionary = new Dictionary<string, string>();
foreach (var row in table.Rows)
{
dictionary.Add(row[0], row[1]);
}
return dictionary;
}
}
and call it from the method.
var dictionary = TableExtensions.ToDictionary(table);
var countryCode = dictionary["countryCode"];
Unfortnally I get error The given key was not present in the dictionary,
since the dictionary only returns two values from the first and the second Key
Ofcourse if I change the keys to row[2], row[3] it gets the right columns.
But I would like to reuse the Table Extension.
Also i tried to increment them, but it only took the first to columns
var i = 0;
foreach (var row in table.Rows)
{
dictionary.Add(row[i], row[i]);
i++;
}
Does anyone has a better solution?
I'm not entirely sure what you want the dictionary to ultimately contain, but as you mention that manually changing the rows it looks for to:
row[2], row[3]
gives the data you want, perhaps this would give you the reusability you're looking for:
public class TableExtensions
{
public static Dictionary<string, string> ToDictionary(Table table, int columnOne, int columnTwo)
{
int i = 0;
var dictionary = new Dictionary<string, string>();
foreach (var row in table.Rows)
{
dictionary.Add(row[columnOne], row[columnTwo]);
}
return dictionary;
}
}
Usage:
var dictionary = TableExtensions.ToDictionary(table, 2, 3);
This produces a dictionary with the following contents:
You could get the country code like this:
foreach (var row in dictionary)
{
var countryCode = row.Key;
var score = row.Value ?? string.empty;
}
Given the simplicity of the country codes I would make them a comma separated list and use a vertical table instead:
When I Perform POST Operation for "/example/"
| Field | Value |
| answerValue1 | ... |
| answerValue2 | ... |
| countryCodes | AD, AF |
| cash | ... |
In your step definition:
var example = table.CreateInstance<ExampleRow>();
// use example.GetCountryCodes();
And the ExampleRow class to parse the table into an object:
public class ExampleRow
{
public string AnswerValue1 { get; set; }
public string AnswerValue2 { get; set; }
private string[] countryCodes;
public string CountryCodes
{
get => string.Join(", ", countryCodes);
set => countryCodes = value.Split(", ");
}
public string[] GetCountryCodes()
{
return countryCodes;
}
}

How to add Sub total row and grand total row using linq

I want to add the sub total row for each BusinessTypeCode and grand total for all BusinessTypeCode. How can I add these two rows in my linq and put the below of each businesstypecode.
MY CURRENT CODE
var query = (from _transaction in _entities.Transactions
join _cd in _entities.Organisations on _transaction.Refno equals _cd.Refno
join _b in _entities.BusinessType on _transaction.BusinessTypeCode equals _b.BusinessTypeCode
group new
{
_trans = _transaction,
cd = _cd,
}
by new { _transaction.BusinessTypeCode,_transaction.Refno, _cd.BusinessName, _b.Description } into _group
orderby _group.Key.BusinessTypeCode
select new
{
BusinessTypeCode = _group.Key.BusinessTypeCode,
BusType = _group.Key.BusinessTypeCode + " - " +_group.Key.Description,
BusName = _group.Key.BusinessName,
BusL = _group.Sum(x=>x._trans.BusL),
BusInterrest = _group.Sum(x => x._trans.BusInterrest),
BusAdmin = _group.Sum(x => x._trans.BusAdmin),
BusPenalty = _group.Sum(x => x._trans.BusPenalty),
TotalBusCollected =_group.Sum(x=>x._trans.TotalBusCollected)
});
DataTable dt=new DataTable();
DataSet ds = new DataSet();
ds.Tables.Add(query.CopyToDataTable());
ds.Tables[0].TableName = "Table1";
var subtotal = query.GroupBy(x=>x.BusinessTypeCode ).Select(s=>new
{
BusinessTypeCode =s.Key,
BusLSub = s.Sum(x=>x.BusL),
BusInterrestSub = s.Sum(x=>x.BusInterrest),
BusAdminSub = s.Sum(x=>x.BusAdmin),
BusPenaltySub = s.Sum(x=>x.BusPenalty),
TotalBusCollectedSub = s.Sum(x=>x.TotalBusCollected),
});
foreach (var a in subtotal)
{
dt = ds.Tables[0];
dt.NewRow();
dt.Rows.Add(a.BusLSub, a.BusInterrestSub, a.BusAdminSub, a.BusPenaltySub, a.TotalBusCollectedSub );
}
return ds;
Current Output
BusType |BusName | BusL |BusInterest|BusAdmin| BusPenalty|TotalBusCollected
1 - ACCOUNTING |HIGHVELD |-23.91 | 0 |-22.84 | 0 |-46.75
1 - ACCOUNTING |BHP |-50.81 |-79.21 |-76 |-20.02 |-226.04
2 - FOOD |SAB |-14.18 |-435.97 |-2.57 |-67.55 |-520.27
2 - FOOD |DISTIL |-43.05 |0 |-66,59 |0 |-109.64
3 - MINING |ANGLOGOLD |-4.43 |0 |-72 |0 |-76.43
-74.72 |-79.21 |-98.84 |-20.02 |-272.79
-57.23 |-435.97 |-69.16 |-67.55 |-629.91
-4.43 |0 |-72 |0 |-76.43
How can I push it into where BusinessTypeCode =BusinessTypeCode ?
OUTPUT SUPPOSE TO BE LIKE
BusType |BusName | BusL |BusInterest|BusAdmin| BusPenalty|TotalBusCollected
1 - ACCOUNTING |HIGHVELD |-23.91 | 0 |-22.84 | 0 |-46.75
1 - ACCOUNTING |BHP |-50.81 |-79.21 |-76 |-20.02 |-226.04
--------------------------+-------+-----------+--------+-----------+-----------------
Sub Total |-74.72 |-79.21 |-98.84 |-20.02 |-272.79
--------------------------+-------+-----------+--------+-----------+-----------------
2 - FOOD |SAB |-14.18 |-435.97 |-2.57 |-67.55 |-520.27
2 - FOOD |DISTIL |-43.05 |0 |-66,59 |0 |-109.64
--------------------------+-------+-----------+--------+-----------+-----------------
Sub Total |-57.23 |-435.97 |-69.16 |-67.55 |-629.91
--------------------------+-------+-----------+--------+-----------+-----------------
3 - MINING |ANGLOGOLD |-4.43 |0 |-72 |0 |-76.43
--------------------------+-------+-----------+--------+-----------+-----------------
Sub Total |-4.43 |0 |-72 |0 |-76.43
--------------------------+-------+-----------+--------+-----------+-----------------
GRAND TOTAL |-136.38|-515.38 |-240 |-87.57 |-979.13
As I am currently on mobile I won't be able to verify this piece of code.
You can do something like this:
foreach(var item in subtotal)
{
var lastRecord = ds.Tables[0].Rows.LastOrDefault(r=>r["BusType"]==item.BusTypeCode);
var lastIndex = ds.Tables[0].Rows.IndexOf(lastRecord);
DataRow dr = ds.Tables[0].NewRow();
dr["BusType"] = item.BusTypeCode;
// etc.
ds.Tables[0].Rows.InserAt(dr, lastIndex);
}
// insert the grand total row at the end
return ds;
This is just to show the idea on how you can transform the current DataSet output to your desired output.
It looks like what you need is some sort of interleaving. I haven't tested this but the idea you want is similar to the following (assuming you have a class called BusRow instead of your anonymous type). I've also taken the liberty of making the subrow's type the same as BusRow, but this need not be the case when you do it.
public static IEnumerable<BusRow> Interleave(List<BusRow> rowItems, List<BusRow> subTotalItems)
{
for(int i = 0 ; i < rowItems.Count; ++i)
{
yield return rowItems[i];
if(i > 0 && rowItems[i].BusinessTypeCode != rowItems[i-1].BusinessTypeCode)
{
yield return subTotalItems.Single(x => x.BusinessTypeCode == rowItems[i-1].BusinessTypeCode);
}
}
yield return subTotalItems.Single(x => x.BusinessTypeCode == rowItems[rowItems.Count-1].BusinessTypeCode);
}
Of course you're going to change your original query to return BusRow like:
var subtotal = query.GroupBy(x=>x.BusinessTypeCode ).Select(s=>new BusRow
{
BusinessTypeCode =s.Key,
BusL = s.Sum(x=>x.BusL),
BusInterrest = s.Sum(x=>x.BusInterrest),
BusAdmin = s.Sum(x=>x.BusAdmin),
BusPenalty = s.Sum(x=>x.BusPenalty),
TotalBusCollected = s.Sum(x=>x.TotalBusCollected),
});
Then call
var interleaved = Interleave(query, subtotal);
foreach (var a in interleaved)
{
dt = ds.Tables[0];
dt.NewRow();
if(String.isNullOrEmpty(a.BusName)//format your subtotal row as you like - add --rows before and after
dt.Rows.Add(a.BusL, a.BusInterrest, a.BusAdmin, a.BusPenalty, a.TotalBusCollected );
else
dt.Rows.Add(a.BusinessTypeCode, a.BusName, a.BusL, a.BusInterrest, a.BusAdmin, a.BusPenalty, a.TotalBusCollected ); //normal row
}

reading a CSV into a Datatable without knowing the structure

I am trying to read a CSV into a datatable.
The CSV maybe have hundreds of columns and only up to 20 rows.
It will look something like this:
+----------+-----------------+-------------+---------+---+
| email1 | email2 | email3 | email4 | … |
+----------+-----------------+-------------+---------+---+
| ccemail1 | anotherccemail1 | 3rdccemail1 | ccemail | |
| ccemail2 | anotherccemail2 | 3rdccemail2 | | |
| ccemail3 | anotherccemail3 | | | |
| ccemail4 | anotherccemail4 | | | |
| ccemail5 | | | | |
| ccemail6 | | | | |
| ccemail7 | | | | |
| … | | | | |
+----------+-----------------+-------------+---------+---+
i am trying to use genericparser for this; however, i believe that it requires you to know the column names.
string strID, strName, strStatus;
using (GenericParser parser = new GenericParser())
{
parser.SetDataSource("MyData.txt");
parser.ColumnDelimiter = "\t".ToCharArray();
parser.FirstRowHasHeader = true;
parser.SkipStartingDataRows = 10;
parser.MaxBufferSize = 4096;
parser.MaxRows = 500;
parser.TextQualifier = '\"';
while (parser.Read())
{
strID = parser["ID"]; //as you can see this requires you to know the column names
strName = parser["Name"];
strStatus = parser["Status"];
// Your code here ...
}
}
is there a way to read this file into a datatable without know the column names?
It's so simple!
var adapter = new GenericParsing.GenericParserAdapter(filepath);
DataTable dt = adapter.GetDataTable();
This will automatically do everything for you.
I looked at the source code, and you can access the data by column index too, like this
var firstColumn = parser[0]
Replace the 0 with the column number.
The number of colums can be found using
parser.ColumnCount
I'm not familiar with that GenericParser, i would suggest to use tools like TextFieldParser, FileHelpers or this CSV-Reader.
But this simple manual approach should work also:
IEnumerable<String> lines = File.ReadAllLines(filePath);
String header = lines.First();
var headers = header.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries);
DataTable tbl = new DataTable();
for (int i = 0; i < headers.Length; i++)
{
tbl.Columns.Add(headers[i]);
}
var data = lines.Skip(1);
foreach(var line in data)
{
var fields = line.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries);
DataRow newRow = tbl.Rows.Add();
newRow.ItemArray = fields;
}
i used generic parser to do it.
On the first run through the loop i get the columns names and then reference them to add them to a list
In my case i have pivoted the data but here is a code sample if it helps someone
bool firstRow = true;
List<string> columnNames = new List<string>();
List<Tuple<string, string, string>> results = new List<Tuple<string, string, string>>();
while (parser.Read())
{
if (firstRow)
{
for (int i = 0; i < parser.ColumnCount; i++)
{
if (parser.GetColumnName(i).Contains("FY"))
{
columnNames.Add(parser.GetColumnName(i));
Console.Log("Column found: {0}", parser.GetColumnName(i));
}
}
firstRow = false;
}
foreach (var col in columnNames)
{
double actualCost = 0;
bool hasValueParsed = Double.TryParse(parser[col], out actualCost);
csvData.Add(new ProjectCost
{
ProjectItem = parser["ProjectItem"],
ActualCosts = actualCost,
ColumnName = col
});
}
}

Create a new row from 2 tables

New to LINQ and c# but having problem with one specific problem.
I have 2 Data tables.
DataTable 1 has Name, House Number in it. DataTable 2 holds Street, FirstHouseNumber, LastHouseNumber.
What I want to do is create a new table that has Name, House Number, Street in it but I keep ending up with lots of DataTable1 Count * DataTable2 Count when I would only expect the same count as DataTable 1.
Heres the expression I am using:
var funcquery = from name in DT1.AsEnumerable()
from street in DT2.AsEnumerable()
where name.Field<int>("Number") >= street.Field<int>("FirstHouseNumber") && name.Field<int>("Number") <= street.Field<int>("LastHouseNumber")
select new { Name = name.Field<string>("Name"), Number = name.Field<int>("Number"), street.Field<string>("Street") };
What am I doing wrong? I just cant get it at all.
TIA.
This will be easier to think about with a more specific example:
Name House Number
------ ------------
Chris 123
Ben 456
Joe 789
Street First House Last House
Number Number
-------- ------------ ------------
Broadway 100 500
Main 501 1000
To demonstrate the results using this data, I use the following code:
class HouseInfo
{
public string Name;
public int HouseNumber;
public HouseInfo(string Name, int HouseNumber)
{
this.Name = Name;
this.HouseNumber = HouseNumber;
}
}
class StreetInfo
{
public string Street;
public int FirstHouseNumber;
public int LastHouseNumber;
public StreetInfo(string Street, int FirstHouseNumber, int LastHouseNumber)
{
this.Street = Street;
this.FirstHouseNumber = FirstHouseNumber;
this.LastHouseNumber = LastHouseNumber;
}
}
static void Main(string[] args)
{
HouseInfo[] houses = new HouseInfo[] {
new HouseInfo("Chris", 123),
new HouseInfo("Ben", 456),
new HouseInfo("Joe", 789) };
StreetInfo[] streets = new StreetInfo[] {
new StreetInfo("Broadway", 100, 500),
new StreetInfo("Main", 501, 1000)};
var funcquery = from name in houses
from street in streets
where name.HouseNumber >= street.FirstHouseNumber && name.HouseNumber <= street.LastHouseNumber
select new { Name = name.Name, Number = name.HouseNumber, Street = street.Street };
var results = funcquery.ToArray();
for (int i = 0; i < results.Length; i++)
{
Console.WriteLine("{0} {1} {2}", results[i].Name, results[i].Number, results[i].Street);
}
}
The results are:
Chris 123 Broadway
Ben 456 Broadway
Joe 789 Main
This seems to be what you are after, so I don't think the problem is in the code you've given. Perhaps it is in the data itself (if multiple streets include the same house range) or something else. You may need to supply more specific data that demonstrates your problem.
Here's the same code implemented using DataTables instead of classes. It yields the same result:
System.Data.DataTable DT1 = new System.Data.DataTable("Houses");
System.Data.DataTable DT2 = new System.Data.DataTable("Streets");
DT1.Columns.Add("Name", typeof(string));
DT1.Columns.Add("Number", typeof(int));
DT2.Columns.Add("Street", typeof(string));
DT2.Columns.Add("FirstHouseNumber", typeof(int));
DT2.Columns.Add("LastHouseNumber", typeof(int));
DT1.Rows.Add("Chris", 123);
DT1.Rows.Add("Ben", 456);
DT1.Rows.Add("Joe", 789);
DT2.Rows.Add("Broadway", 100, 500);
DT2.Rows.Add("Main", 501, 1000);
var funcquery = from System.Data.DataRow name in DT1.Rows
from System.Data.DataRow street in DT2.Rows
where (int)name["Number"] >= (int)street["FirstHouseNumber"]
&& (int)name["Number"] <= (int)street["LastHouseNumber"]
select new { Name = name["Name"], Number = name["Number"], Street = street["Street"] };
var results = funcquery.ToArray();
for (int i = 0; i < results.Length; i++)
{
Console.WriteLine("{0} {1} {2}", results[i].Name, results[i].Number, results[i].Street);
}

Ratios with Datatable

I have an application that I am making an have run into a place where I got stuck. My problem is this: how can I create a ratio with a datatable.
Ex:
My Table:
|gh | gh | tf | tf|
|tf | tf | tf | tf|
|gh | gh | tf | tf|
My output:
4 gh:8 tf
Anything could be in the table, so I was hoping to use Linq to determine the ratio .. but didnt know how to find out all the diffrent results in the table.
Maybe something like this would work?
private Dictionary<String, Int32> GetCounts(DataTable dt)
{
var result = new Dictionary<String, Int32>();
foreach (DataRow row in dt.Rows)
{
foreach (var v in row.ItemArray)
{
string key = (v ?? string.Empty).ToString();
if (!result.ContainsKey(key))
{
result.Add(key, 0);
}
result[key]++;
}
}
return result;
}

Categories