Linq group by multiple columns, one of which is selectable - c#

I have a table consisting of
MyDate(DATETIME),
Type nvarchar(255),
PropertyAId(int),
PropertyBId(int),
Data1 (float),
Data2 (float),
...
Data50 (float)
I want to return a table grouped by
MyDate,Type, and one of PropertyAId,PropertyBId (depending on user selection)
and sum all of the Data columns.I would prefer summing and selecting Data columns based on prefix or data type, and not having to repeat the same line 50 times
What I have so far is a bit ugly
DataTable dt2 = dt.Clone();
var grouped = dt.AsEnumerable().
GroupBy(r => new
{
MyDate = r.Field<DateTime>("MyDate"),
PropertyAId = selectedGroupingColumn == "PropertyAId" ? r.Field<int?>("PropertyAId") : null,
PropertyBId = selectedGroupingColumn == "PropertyBId" ? r.Field<int?>("PropertyBId") : null,
Type = r.Field<string>("Type")
});
foreach (var group in grouped)
{
DataRow row = dt2.NewRow();
foreach (var col in dt.Columns.Cast<DataColumn>())
{
if (col.ColumnName.StartsWith("Data"))
{
double sum = 0;
if (col.DataType == typeof(double))
sum = group.Sum(r => r.Field<double>(col));
row.SetField(col.ColumnName, sum);
}
else
row[col.ColumnName] = group.First()[col];
}
dt2.Rows.Add(row);
}
//dt2.Columns.Remove unselected property and return table

Your logic seems (mostly) sound, but I would suggest just using selectedGroupingColumn directly to get the value to group by, and only set the sum for "Data" columns of type double:
DataTable dt2 = dt.Clone();
var grouped = dt.AsEnumerable().
GroupBy(r => new {
MyDate = r.Field<DateTime>("MyDate"),
GroupingColumnValue = r.Field<int?>(selectedGroupingColumn),
Type = r.Field<string>("Type")
});
foreach (var group in grouped) {
DataRow row = dt2.NewRow();
foreach (var col in dt.Columns.Cast<DataColumn>())
if (col.ColumnName.StartsWith("Data") && col.DataType == typeof(double))
row.SetField(col.ColumnName, group.Sum(r => r.Field<double>(col)));
else
row[col.ColumnName] = group.First()[col];
dt2.Rows.Add(row);
}

Related

Get column name by value of field in datarow

My question is actually more about optimizing something I already have working.
I'm having a hard time believing there isn't a better way to do this with a LINQ query or lambda expression, so I thought I'd try here.
Each of my datatable rows has an item number and 43 quantity columns, that each correspond with a specific day. What I'm trying to do is take each row, and find the first quantity column that is greater than 0 and return that column name. My solution does work, but I'd really like to make it more efficient:
foreach (DataRow r in dt.Rows)
{
for (int i = 3; i <= dt.Columns.Count - 1; i++)
{
tempCol = dt.Columns(i).ColumnName.ToString();
rowValue = Convert.ToInt32(r(tempCol));
if (rowValue > 0)
{
tempCol = tempCol.Replace("Apat", "");
break;
}
}
var FirstAvailableDate = WorkDate.AddDays((dec)tempCol).ToShortDateString;
//use data in someway
}
Thanks for any suggestions ahead of time!!
the current code, each row * each column
get name of column
store it in variable
in match case perform String.Replace
my suggestion:
var allCols = dt.Columns
.Cast<DataColumn>()
.Select(col => col.ColumnName.Replace("Apat", ""))
.ToArray();
foreach (DataRow r in dt.Rows)
{
var firstCol =
r.ItemArray.Select((cell, position) => Tuple.Create(Convert.ToInt32(cell), position))
.FirstOrDefault(tuple => tuple.Item1 > 0);
if(firstCol == null) continue;
var colName = allCols[firstCol.Item2];
var FirstAvailableDate = WorkDate.AddDays((dec)colName).ToShortDateString;
//use data in someway
}
Please change following code
Tuple.Create(Convert.ToInt32(position), cell)
var colName = allCols[firstCol.Item1];
Working fine...!!!

Getting an error on pivot table using linq

I did pivot table from normal datatable using linq. However sometimes, i get an errors like;
There is no row at position 5(or any number like 7,8,11 etc..)
-----------------------------------------------------------
object reference not set to an instance of an object
What i didn't understand it, Why sometimes it works very well, with the same conditions like 'sample_time' etc. and why sometimes doesn't work.
Here is the my code to swap rows to columns with linq. Also when i debug, I found the part that throws an error.
var dtExist = dtTopAll.AsEnumerable().Where(l => l.Field<DateTime>("SAMPLE_TIME") >= minDate && l.Field<DateTime>("SAMPLE_TIME") <= maxDate).Any();
if (dtExist == true)
{
var dt = (from dr1 in dtTopAll.AsEnumerable()
where dr1.Field<DateTime>("SAMPLE_TIME") >= minDate && dr1.Field<DateTime>("SAMPLE_TIME") <= maxDate
group dr1 by new
{
WAIT_CLASS = dr1.Field<string>("WAIT_CLASS"),
SAMPLE_TIME = dr1.Field<DateTime>("SAMPLE_TIME")
} into g
select new
{
SAMPLE_TIME = g.Key.SAMPLE_TIME,
WAIT_CLASS = g.Key.WAIT_CLASS,
WAITS = g.Sum(z => z.Field<double>("WAITS") / 100)
}).ToDataTable();
var groups = dt.AsEnumerable().GroupBy(x => x.Field<DateTime>("SAMPLE_TIME")).ToList();
foreach (var group in groups) // From this line to end, throws an error .
{
DataRow newRow = dtPivotCustom.Rows.Add();
newRow[0] = group.Key;
foreach (string item in items)
{
newRow[item] = group.Where(x => x.Field<string>("WAIT_CLASS") == item).Select(x => x.Field<double>("WAITS")).Sum();
}
}
}

How to add value to a value of certain clolumn and row in datatable C#

I have two datatables:
1.dtEmployee:
|agent_id|agent_name|sum|
2.dtReport:
|sale_date|agent_id|sum |
------------------------
For each record in dtReport I need to find agent_id in dtEmployee and add the value of dtReport["sum"] to dtEmployee["sum"]:
foreach (DataRow r in dtReport)
{
DataRow empRow = dtEmployee.find(dtReport["agent_id"]);
empRow["sum"] += r["sum"];
}
Is there a way that would allow me to accomplish this?
Something like this works:
private void AddValue(string agent_id, decimal sum)
{
DataRow[] row= dtEmployee.Select("agent_id= '"+agent_id+"'");
//since only one record with this agent_id, we take first record of array -> row[0]
decimal dSum= Convert.ToDecimal(row[0][column]);
dSum+= sum;
row[0]["sum"] = dSum;
}
and insert this function into loop:
foreach (DataRow r in dtReport)
{
AddValue(r["agent_id"], r["sum"]);
}
This can be achieved in many ways.
Option 1 :
foreach(DataRow row in dtEmployee.Rows)
{
var update = dtReport.AsEnumerable().FirstOrDefault(r => r.Field<string>("agent_id") == row.Field<string>("agent_id"));
if(update !=null)
row.SetField<float>("sum", update.Field<float>("sum"));
}
Option 2
Another option would be creating new table by joining DataTables
var results = from t1 in dtEmployee.AsEnumerable()
join t2 in dtReport.AsEnumerable()
on t1.Field<int>("agent_id") equals t2.Field<int>("agent_id")
select new { t1, t2 };
// Now we can construct new DataTable
DataTable result = new DataTable() ;
result.Columns.Add("agent_id", typeof(System.Int32));
result.Columns.Add("Name", typeof(System.String));
result.Columns.Add("sum", typeof(float));
foreach(var dr in results )
{
DataRow newRow = results.NewRow();
newRow["agent_id"] = dr.t1.Field<int>("agent_id");
newRow["agent_name"] = dr.t1.Field<string>("agent_name");
newRow["sum"] = dr.t2.Field<float>("sum");
// When all columns have been filled in then add the row to the table
results.Rows.Add(newRow);
}
Working sample
Hope this helps !
You could try something like this. Given that your agent_id and sum are integer.
foreach (DataRow r in dtReport.Rows)
{
dtEmployee.Select(string.Format("agent_id = {0}", r["agent_id"])).ToList<DataRow>().ForEach(
v => { v["sum"] = (v.IsNull("sum") ? 0 : v.Field<int>("sum")) + (r.IsNull("sum") ? 0 : r.Field<int>("sum")); });
}
Or equivalent code
foreach (DataRow r in dtReport.Rows)
{
DataRow[] empRow = dtEmployee.Select("agent_id = " + r["agent_id"]);
for (int i = 0; i < empRow.Length; i++)
{
empRow[i]["sum"] = (empRow[i].IsNull("sum") ? 0 : (int)empRow[i]["sum"]) + (r.IsNull("sum") ? 0 : (int)r["sum"]);
}
}

How to find rowindex of a datatable into another datatable?

I am using C#. I have two data tables and i want to find the rows of first data table into second data table.
Example.
First data table's data:
1 inam
2 sohan
Second data tables's data:
3 ranjan
1 inam
2 sohan
Now i want to know the index of first two rows of first data table into second data table.
Please help guys.
Any answer or advice
Best Regards
You can use following extension method which returns the first index of a "sub-sequence":
// I've used String.Join to get something that is comparable easily
// from the ItemArray that is the object-array of all fields
IEnumerable<string> first = table1.AsEnumerable()
.Select(r => string.Join(",",r.ItemArray)); //
IEnumerable<string> second = table2.AsEnumerable()
.Select(r => string.Join(",", r.ItemArray));
int index = second.IndexOfSequence(first, null); // 1
Here the extension:
public static int IndexOfSequence<TSource>(this IEnumerable<TSource> input, IEnumerable<TSource> sequence, IEqualityComparer<TSource> comparer)
{
if (input == null) throw new ArgumentNullException("input");
if (sequence == null) throw new ArgumentNullException("sequence");
if (!sequence.Any()) throw new ArgumentException("Sequence must not be empty", "sequence");
if (comparer == null)
{
comparer = EqualityComparer<TSource>.Default;
}
int index = -1;
int firstIndex = -1;
bool found = false;
TSource first = sequence.First();
using (IEnumerator<TSource> enumerator = input.GetEnumerator())
{
using (IEnumerator<TSource> enumerator2 = sequence.GetEnumerator())
{
enumerator2.MoveNext();
while (enumerator.MoveNext())
{
index++;
found = comparer.Equals(enumerator.Current, enumerator2.Current);
if (found && firstIndex == -1) firstIndex = index;
if (found && !enumerator2.MoveNext())
return firstIndex;
}
}
}
return -1;
}
tested with this sample data:
var table1 = new DataTable();
table1.Columns.Add("ID", typeof(int));
table1.Columns.Add("Name");
var table2 = table1.Clone();
table1.Rows.Add(1, "inam");
table1.Rows.Add(2, "Sohan");
table2.Rows.Add(3, "ranjan");
table2.Rows.Add(1, "inam");
table2.Rows.Add(2, "Sohan");
If you don't have much volume this might work....
var tableOneIndex = -1;
var tableTwoIndex = -1;
foreach (var tableOneRow in tableOne.Rows)
{
tableOneIndex++;
foreach (var tableTwoRow in tableTwo.Rows)
{
tableTwoIndex++;
if (tableOneRow["name"].ToString() == tableTwoRow["name"].ToString())
{
// Do whatever you wanted to do with the index values
}
}
}
As a simple solution, this should suffice:
// Create and populate data tables
DataTable dataTable1 = new DataTable();
dataTable1.Columns.Add("Name", typeof(string));
DataRow row1 = dataTable1.NewRow();
row1["Name"] = "Inam";
DataRow row2 = dataTable1.NewRow();
row2["Name"] = "Sohan";
dataTable1.Rows.Add(row1);
dataTable1.Rows.Add(row2);
DataTable dataTable2 = new DataTable();
dataTable2.Columns.Add("Name", typeof(string));
DataRow row3 = dataTable2.NewRow();
row3["Name"] = "Ranjan";
DataRow row4 = dataTable2.NewRow();
row4["Name"] = "Inam";
DataRow row5 = dataTable2.NewRow();
row5["Name"] = "Sohan";
dataTable2.Rows.Add(row3);
dataTable2.Rows.Add(row4);
dataTable2.Rows.Add(row5);
// Loop through rows in first table
foreach (DataRow row in dataTable1.Rows)
{
int rowIndexInSecondTable = 0;
// Loop through rows in second table
for (int i = 0; i < dataTable2.Rows.Count; i++)
{
// Check if the column values are the same
if (row["Name"] == dataTable2.Rows[i]["Name"])
{
// Set the current index and break to stop further processing
rowIndexInSecondTable = i;
break;
}
}
// The index of the row in the second table is now stored in the rowIndexInSecondTable variable, use it as needed, for example, writing to the console
Console.WriteLine("Row with name '" + row["Name"] + "' found at index " + rowIndexInSecondTable.ToString());
}

Update a DataTable in C# without using a loop?

Let suppose there are three columns in my DataTable
code
name
color
If I know the code and name, how can I update the color of that specific row whose code and name match my criteria? I want to do this without using Loops!
You can use LINQ:
DataRow dr = datatable.AsEnumerable().Where(r => ((string)r["code"]).Equals(someCode) && ((string)r["name"]).Equals(someName)).First();
dr["color"] = someColor;
Of course I'm assuming all those criteria are strings. You should change the casts to the correct types.
// Use the Select method to find all rows matching the name and code.
DataRow[] rows = myDataTable.Select("name 'nameValue' AND code = 'codeValue');
for(int i = 0; i < rows.Length; i ++)
{
rows[i]["color"] = colorValue;
}
DataTable recTable = new DataTable();
// do stuff to populate table
recTable.Select(string.Format("[code] = '{0}' and [name] = '{1}'", someCode, someName)).ToList<DataRow>().ForEach(r => r["Color"] = colorValue);
With LINQ:
var dataRows = dt.AsEnumerable().Select(c => { c["color"] = c["Code"].ToString() == "1" ? "Red" : "White"; return c; });
dt = dataRows.CopyToDataTable();
You could do:
foreach (DataRow row in datatable.Rows)
{
if(row["code"].ToString() == someCode && row["name"].ToString() == someName)
{
row["color"] = someColor;
}
}

Categories