Get DataTime DataColumn of DataTable to List - c#

I want to get a DataColumn (of DataTime type) of my DataTable in List. How can I do this with LINQ?
I tried the following lines but didn't work:
DateTimeList = dt.Columns.Cast<DataColumn>()
.Where(dc => dc.DataType == typeof(DateTime)).ToList());
DateTime values are created like the follow one:
new DateTime(2019, 6, 17, 16, 46, 05)

To return a List<DateTime> type from the DateTime columns in a DataTable:
var dates = dt.Columns.Cast<DataColumn>()
.Where(c => c.DataType == typeof(DateTime))
.SelectMany(c => c.Table.Rows.Cast<DataRow>()
.Select(r => r.Field<DateTime>(c.ColumnName))).ToList();
The query gets all the DateTime values whether the dt contains one or more Column<DateTime> type.
If you have a single DataColumn<DateTime> type in the table, you can write instead:
var dates = dt.Rows
.Cast<DataRow>()
.Select(r => r.Field<DateTime>(dt.Columns.Cast<DataColumn>()
.FirstOrDefault(c => c.DataType == typeof(DateTime))?.ColumnName)).ToList();

Related

C# Linq Datatable groupby datetime month

I have a datatable that looks like this
The output I'm trying to achieve is described in the picture, where I want to group by the month of the datetime, and agent I suppose. This is what I have so far.
DataTable dtTemp = new DataTable();
dtTemp.Columns.Add("Agent", typeof(string));
dtTemp.Columns.Add("Amount", typeof(decimal));
dtTemp.Columns.Add("Date", typeof(DateTime));
dtTemp = dtTemp.AsEnumerable()
.GroupBy(r => r[0])
.Select(g =>
{
var row = dt.NewRow();
row[0] = g.Key;
row[1] = g.Sum(r => (decimal)r[1]);
return row;
})
.CopyToDataTable();
Any ideas how to achieve this?
Thanks in advance!
From analyzing your post, you need to group by data table rows by column Agent and only month and year from column Date.
You need to take one temporary data table dt that can hold data type for each column in your resultant data table result.
So CopyToDataTable() will copy all of your dtTemp group data to new result data table with temporary dt columns data type.
DataTable dt = new DataTable();
dt.Columns.Add("Agent", typeof(string));
dt.Columns.Add("Amount", typeof(decimal));
dt.Columns.Add("Date", typeof(string));
DataTable result = dtTemp.AsEnumerable()
.Select(x => new
{
Agent = x.Field<string>("Agent"),
Amount = x.Field<decimal>("Amount"),
Date = x.Field<DateTime>("Date").ToString("MM-yyyy")
})
.GroupBy(x => new { x.Agent, x.Date })
.Select(g =>
{
var r = dt.NewRow();
r["Agent"] = g.Key.Agent;
r["Amount"] = g.Sum(c => c.Amount);
r["Date"] = g.FirstOrDefault().Date;
return r;
})
.CopyToDataTable();
Output:
var temp = dtTemp.AsEnumerable().GroupBy(grp => new { grpmonth = Convert.ToDateTime(grp["Date"]).Month, grpyear = Convert.ToDateTime(grp["Date"]).Year, grpagent = grp["Agent"] })
.Select(val =>
{
var row = dtTemp.NewRow();
row["Agent"] = val.FirstOrDefault()["Agent"];
row["Amount"] = val.Sum(amt => Convert.ToDecimal(amt["Amount"]));
row["Date"] = val.FirstOrDefault()["Date"];
return row;
}
)
.CopyToDataTable();
For reference
So from your input sequence, you want all used Agents, with the total sum of the Amounts per month.
Let's assume your DataTable is a sequence of Rows, and that it easily can be converted to a sequence of Rows:
class RowData
{
public string Agent {get; set}
public DateTime Date {get; set;}
public int Amount {get; set;}
}
IEnumerable<RowData> tableData = ...
The solution if your problem is to make groups of RowData with equal value for Agent, and to group these groups again to make subgroups with equal value for year and month
var AgentsWithAmountsPerMonth = tableData
.GroupBy(row => row.Agent, // make groups of rows with same Agent
// ResultSelector: get the Agent (=key), with all rows that have this Agent
(agent, rowsWithThisAgent) => new
{
Agent = agent,
// to calculate the totals per year/month, extract the year / month / amount
TotalsPerMonth = rowsWithThisAgent.Select(row => new
{
Year = row.Date.Year,
Month = row.Date.Month,
Amount = row.Amount,
})
// and group by same Year / Month:
.GroupBy(row => new {row.Year, row.Month},
// ResultSelector
(yearMonth, rowsWithThisYearMonth) => new
{
Year = yearMonth.Year,
Month = yearMonth.Month,
Total = rowsWithThisYearMont.Select(row => row.Amount).Sum(),
// Or put the year and month in one field:
Month = new DateTime(yearMonth.Year, yearMonth.Month, 1),
},
});
});

Add missing dates to list

I have written a solution which basically adds missing date and sets the sales property for that date in my collection to 0 where it's missing like this:
int range = Convert.ToInt32(drange);
var groupedByDate = tr.Union(Enumerable.Range(1, Convert.ToInt32(range))
.Select(offset => new MyClassObject
{
Date = DateTime.Now.AddDays(-(range)).AddDays(offset),
Sales = 0
})).GroupBy(x => x.Date)
.Select(item => new MyClassObject
{
Sales = item.Sum(x => x.Sales),
Date = item.Key
})
.OrderBy(x => x.Date)
.ToList();
The first solution where the dates from DB were grouped by and they were missing looked like this:
var groupedByDate = tr
.GroupBy(x => x.TransactionDate.Date)
.Select(item => new MyClassObject
{
Sales = item.Sum(x => x.QuantityPurchased),
Date = item.Key.ToString("yyyy-MM-dd")
})
.OrderBy(x => x.Date)
.ToList();
I don't really like the way I did it in first solution, the code looks very messy and I honestly believe it can be written in a better manner..
Can someone help me out with this?
P.S. The first solution above that I've shown works just fine, but I would like to write something better which is more prettier to the eyes, and it looks quite messy (the first solution I wrote)...
How about generate the date range and then left join that with the result from your original query. And than set Sales to 0 when there is no match.
int range = 2;
var startDate = DateTime.Now;
var dates = Enumerable.Range(1, range)
.Select(offset => startDate.AddDays(-offset).Date);
var groupedByDate = from date in dates
join tmp in groupedByDate on date equals tmp.Date into g
from gr in g.DefaultIfEmpty()
select new MyClassObject
{
Sales = gr == null ? 0 : gr.Sales,
Date = date
};
Here is the easy way to do this:
var lookup = tr.ToLookup(x => x.TransactionDate.Date, x => x.QuantityPurchased);
var quantity = lookup[new DateTime(2017, 6, 29)].Sum();
If you want a range of dates then it's just this:
var startDate = new DateTime(2017, 6, 1)
var query =
from n in Enumerable.Range(0, 30)
let TransactionDate = startDate.AddDays(n)
select new
{
TransactionDate,
QuantityPurchases = lookup[TransactionDate].Sum(),
};
Simple.

Remove rows with same column value from DataTable and add corresponding values

I have a DataTable with multiple columns. If the value of certain column repeats, I need to remove that row and add the quantities against it. For example, following datatable
ITEM QTY
------------
1 20
2 10
2 10
3 20
would become:
ITEM QTY
-----------
1 20
2 20
3 20
This is what I did
var table = dt.AsEnumerable()
.GroupBy(row => row.Field("ITEM"))
.Select(group => group.First())
.CopyToDataTable();
It removes the extra row but doesn't add up the quantities. So please help me in this regard.
You can use Sum. You just have to find the duplicate-rows first:
var dupGroups = dt.AsEnumerable()
.GroupBy(row => row.Field<int>("ITEM"))
.Where(g => g.Count() > 1);
Now you can use them to get the sum and to remove the redundant rows from the table.
foreach (var group in dupGroups)
{
DataRow first = group.First();
int sum = group.Sum(r => r.Field<int>("QTY"));
first.SetField("QTY", sum);
foreach (DataRow row in group.Skip(1))
dt.Rows.Remove(row);
}
Or in one query which creates a new DataTable.
DataTable newTable = dt.AsEnumerable()
.GroupBy(row => row.Field<int>("ITEM"))
.Select(g =>
{
DataRow first = g.First();
if (g.Count() > 1)
{
int sum = g.Sum(r => r.Field<int>("QTY"));
first.SetField("QTY", sum);
}
return first;
})
.CopyToDataTable();
However, even the second approach modifies the original table which might be undesired since you use CopyToDatatable to create a new DataTable. You need to clone the original table(DataTable newTable = dt.Clone();) to get an empty table with the same schema. Then use NewRow + ItemArray.Clone() or table.ImportRow to create a real clone without modifying the original data.
See: C# simple way to copy or clone a DataRow?
Edit: Here is an example how you can create a clone without touching the original table:
DataTable newTable = dt.Clone();
var itemGroups = dt.AsEnumerable()
.GroupBy(row => row.Field<int>("ITEM"));
foreach (var group in itemGroups)
{
DataRow first = group.First();
if (group.Count() == 1)
newTable.ImportRow(first);
else
{
DataRow clone = newTable.Rows.Add((object[])first.ItemArray.Clone());
int qtySum = group.Sum(r => r.Field<int>("QTY"));
clone.SetField("QTY", qtySum);
}
}
var table = dt.AsEnumerable()
.GroupBy(row => row.Field<int>("ITEM"))
.Select(group => {
var row = group.First();
row['QTY'] = group.Sum(x => x.Field<int>('QTY'));
return row;
}).CopyToDataTable();
This won't change your original DataTable:
var table = dt.Copy().AsEnumerable()
.GroupBy(row=>row["ITEM"])
.Select(g=> {
DataRow dr = g.First();
dr.SetField("QTY", g.Sum(x=>x.Field<int>("QTY")));
return dr;
})
.CopyToDataTable();

Nested LINQ query to select 'previous' value in a list

I have a list of dates. I would like to query the list and return a list of pairs where the first item is a date and the second is the date which occurs just before the first date (in the list).
I know this could easily be achieved by sorting the list and getting the respective dates by index, I am curious how this could be achieved in LINQ.
I've done this in SQL with the following query:
SELECT Date,
(SELECT MAX(Date)
FROM Table AS t2
WHERE t2.Date < t1.Date) AS PrevDate
FROM Table AS t1
It is easy as converting your current query into a LINQ query:
var result = table.Select(x =>
new
{
Date = x.Date,
PrevDate = table.Where(y => y.Date < x.Date)
.Select(y => y.Date)
.Max()
});
List<DateTime> dates = new List<DateTime>()
{
DateTime.Now.AddDays(1),
DateTime.Now.AddDays(7),
DateTime.Now.AddDays(3),
DateTime.Now.AddDays(6),
DateTime.Now.AddDays(5),
DateTime.Now.AddDays(2),
DateTime.Now.AddDays(3),
};
dates = dates.OrderByDescending(x => x).ToList();
var result = dates.Skip(1)
.Select((x, i) => new { Date = dates[i], PreviousDate = x });

how to find the max in a datacolumn of datetime?

I have a DataColumn of DateTime, I would like to know how I can have only the sooner date (min) and the later date (max).
Thanks
object maxDate = dataTable.Compute("MAX(TheDateColumnName)", null);
object minDate = dataTable.Compute("MIN(TheDateColumnName)", null);
This would give what you are looking for:
// Initial Code for Testing
DataTable dt = new DataTable();
dt.Columns.Add("Dates", typeof(DateTime));
dt.Rows.Add(new object[] { DateTime.Now });
dt.Rows.Add(new object[] { DateTime.Now.AddDays(1) });
dt.Rows.Add(new object[] { DateTime.Now.AddDays(2) });
This is the code you would use:
// Actual Code
DataColumn col = dt.Columns[0]; // Call this the one you have
DataTable tbl = col.Table;
var first = tbl.AsEnumerable()
.Select(cols => cols.Field<DateTime>(col.ColumnName))
.OrderBy(p => p.Ticks)
.FirstOrDefault();
var last = tbl.AsEnumerable()
.Select(cols => cols.Field<DateTime>(col.ColumnName))
.OrderByDescending(p => p.Ticks)
.FirstOrDefault();
To add to the answer from kyle, isn't it easier to just do:
for greatest date:
var last = tbl.AsEnumerable()
.Max(r => r.Field<DateTime>(col.ColumnName));
and for earliestdate:
var first = tbl.AsEnumerable()
.Min(r => r.Field<DateTime>(col.ColumnName));
Just retreive a List of DateTime from your DataColumn,
Foreach row in your DataColumn add the current element to your List of DateTime.
List<DateTime> and use Sort method then get the first and the last values.
Depending of your framework version, for 2.0 use above, for >=3.5
you can use Max and Min
or
With linq .OrderByDesc(p => p.X).FirstOrDefault(); on your DateTime List
DataTable dt = new DataTable("MyDataTable");
DataColumn dc = new DataColumn("DateColumn");
dc.DataType = typeof(DateTime);
dt.Columns.Add(dc);
for (int i = 0; i <= 5; i++)
{
DataRow newRow = dt.NewRow();
newRow[0] = DateTime.Now.AddDays(i);
dt.Rows.Add(newRow);
}
DateTime maxDate =
Convert.ToDateTime(
((from DataRow dr in dt.Rows
orderby Convert.ToDateTime(dr["DateColumn"]) descending
select dr).FirstOrDefault()["DateColumn"]
)
);
DateTime minDate =
Convert.ToDateTime(
((from DataRow dr in dt.Rows
orderby Convert.ToDateTime(dr["DateColumn"]) ascending
select dr).FirstOrDefault()["DateColumn"]
)
);
Simpliest for me:
make a dataset with a Table "Tablename" and a column "itemDate" ,set Datatyp System.DateTime
you can read all elements in a list of datetime and search for minDate
Dim List_Date As New List(Of DateTime)
Dim minDate As DateTime
For Each elem As DataRow In DataSet1.Tables("Tablename").Rows
List_Date.Add(elem.Item("itemDate"))
Next
minDate = List_Date.Min

Categories