LINQ group-by and then display datetime for (dd mmm) - c#

I want Linq to group by date but display in text
here is my code
var groups = _uow.Orders.GetAll()
.Where(x => x.Created > baselineDate)
.GroupBy(x => x.Created.ToString("yyyy-MM-dd"));
var orders = new
{
Day = groups.Select(g => g.Key).ToArray(),
Total = groups.Select(g => g.Sum(t => t.Total)).ToArray(),
};
the result is (not good to put in label of graph)
{"Day": [2, 3, 4, 5], "Total": [9999.00, 9999.00, 9999.00, 9999.00] }
But i want this (Monthly)
"Day": ['Jan', Feb', 'Mar', 'Apr'], "Total": [9999.00, 9999.00, 9999.00, 9999.00] }
Or Daily
"Day": ['Jan 1', 'Jan 2', 'Jan 3', 'Jan 4'], "Total": [9999.00, 9999.00, 9999.00, 9999.00] }
Please advice me for DateTime.ToString() that i can play with.
Thank you all.

Thank you all for helping.
I found the answer.
add
.AsEnumerable()
AND
.GroupBy(x => x.Created.ToString("MMM d"));
here is full code
var groups = _uow.Orders.GetAll()
.Where(x => x.Created > baselineDate)
.AsEnumerable()
.GroupBy(x => x.Created.ToString("MMM d"));
var orders = new
{
Day = groups.Select(g => g.Key).ToArray(),
Total = groups.Select(g => g.Sum(t => t.Total)).ToArray(),
};

This question describes how to convert a date time to the string name of the month.
EDIT: for EF, we have to pull everything into memory before doing any string logic:
var orders = _uow.Orders.GetAll()
.Where(x => x.Created > baselineDate)
// pull into memory here, since we won't be making the result set any smaller and
// we want to use DateTime.ToString(), which EF won't compile to SQL
.AsEnumerable()
// this will group by the whole date. If you only want to group by part of the date,
// (e. g. day or day, month), you could group by x => x.Date.Month or the like
.GroupBy(x => x.Date)
.Select(g => new {
// or use just "MMM" for just the month
Dates = g.Select(g => g.Key.ToString("MMM d")).ToArray(),
Total = ...
});

You need to use functions that EF can understand and translate to SQL, such as EntityFunctions.TruncateTime. Here is your updated code:
var groups = _uow.Orders.GetAll()
.Where(x => x.Created > baselineDate)
.GroupBy(x => EntityFunctions.TruncateTime(x.Created));
Then use the string formatting just in the output, but make sure you're doing it after the value has been returned to .Net, using AsEnumerable to switch to Linq-to-Objects:
var orders = new
{
Day = groups.Select(g => g.Key)
.AsEnumerable()
.Select(x => x.ToString("MMM d"))
.ToArray(),
Total = groups.Select(g => g.Sum(t => t.Total)).ToArray(),
};

Related

Additional square brackets in Linq result

In my DB, I have the following data:
track_id, point_id, latd, lond
A,1,12,100
A,2,13,101
B,1,10,90
B,2,13,90
I am trying to generate a IEnumerable<object[]> as follow:
[[12, 100],[13, 101]], [[10, 90],[13, 90]]
Which will be used in a multilinestring Geojson:
This is what I have tried:
var multiTrackList = _context.tracks
.GroupBy(g => g.track_id)
.Select(s => new object[] {
_context.tracks
.Where(w => w.track_id == s.Key)
.OrderBy(o => o.track_id).ThenBy(o => o.date).ThenBy(o => o.time)
.Select(e => new object[] { e.lond.Value, e.latd.Value }).ToList()
});
but it keeps returning:
[[[12, 100],[13, 101]]], [[[10, 90],[13, 90]]]
with extra unneeded square brackets. I don't see what I am doing wrong. Please help.
I'm not sure what you're trying to accomplish with all the grouping and ordering, but you're doing too many selects on your data source, generating [2][2][2] arrays. Maybe this will point you in the right direction:
context _context = new();
_context.tracks = new();
_context.tracks.Add(new track("A", 12, 100));
_context.tracks.Add(new track("A", 13, 101));
_context.tracks.Add(new track("B", 10, 90));
_context.tracks.Add(new track("B", 13, 90));
var multiTrackList = _context.tracks
.GroupBy(g => g.track_id) //grouping
.SelectMany(e => e.Where(x => x.track_id == e.Key)) //selection where track_id equals Keys on the grouped elements
.OrderBy(o => o.track_id) //ordering, I removed the date and time for simplicity
.Select(y => new object[2] { y.latd, y.lond }) //then you can generate your 2 positions array
.ToList(); //finally you can convert the IEnumerable into a List, or whatever you need
Console.Write(JsonConvert.SerializeObject(multiTrackList)); //"[[12,100],[13,101],[10,90],[13,90]]"
Thanks to #james-braz and after editing his answer, I was able to get the correct linq query:
var multiTrackList = _context.tracks
.GroupBy(g => g.track_id ) //grouping
.Select(e =>
e.Where(x => x.cruise_id == e.Key) //selection where track_id equals Keys on the grouped elements
.OrderBy(o => o.date).ThenBy(o => o.time) //ordering, I removed the date and time for simplicity
.Select(y => new object[2] { y.latd, y.lond })//then you can generate your 2 positions array
).ToList(); //finally you can convert the IEnumerable into a List, or whatever you need
I obtain the correct output ([[12,100],[13,101],[10,90],[13,90]]), I can use to update the coordinates property of my multilinestring GeoJSON

LINQ Group by and fetching date by descending order in select list

I'm trying to figure out why does my LINQ isn't working as I want it:
Products.GroupBy(x => x.ItemID).Select(x => new ResultItem()
{
ID = x.Key,
SaleNumber = x.Sum(y => y.QuantityPurchased),
SaleEarning = x.Sum(y => y.QuantityPurchased * y.SalePrice),
Title = x.Select(y => y.Title).FirstOrDefault(),
CurrentPrice = x.OrderByDescending(y => y.TransactionDate)
.Select(y => y.SalePrice).FirstOrDefault(),
SalePrice = x.Select(y => y.SalePrice).FirstOrDefault()
}).ToList();
As you can see I have a list of products where I group them by ItemID and then I'm trying to set for each item their price according to the latest date that they were sold.
This currently gives me completely wrong results for CurrentPrice column in select list... What am I doing wrong here?
P.S. This is the problematic part:
CurrentPrice = x.OrderBy(y => y.TransactionDate).Select(y => y.SalePrice).FirstOrDefault()
Any ideas guys?
P.S2:
Btw. guys the dates that I get in return are like this: 1/1/0001 12:00:00 AM ... In DB the dates are all set correct and in C# they are datetime type...
And when I do something like this:
productTransactions.Where(x => x.ItemID == "//some id")
.OrderByDescending(x => x.TransactionDate).ToList();
I get the correct results. The dates get ordered exactly like I want them , but in this group by they are completely messed up..

Get top n rows and sum the rest and call it others in Entity Framework linq lambda query

My data structure:
BrowserName(Name) Count(Y)
MSIE9 7
MSIE10 8
Chrome 10
Safari 11
-- and so on------
What I'm trying to do is get the top 10 and then get the sum of rest and call it 'others'.
I'm trying to get the others as below but geting error..
Data.OrderBy(o => o.count).Skip(10)
.Select(r => new downModel { modelname = "Others", count = r.Sum(w => w.count) }).ToList();
The error is at 'r.Sum(w => w.count)' and it says
downModel does not contain a definition of Sum
The downModel just has string 'modelname' and int 'count'.
Any help is sincerely appreciated.
Thanks
It should be possible to get the whole result - the top ten and the accumulated "others" - in a single database query like so:
var downModelList = context.Data
.OrderByDescending(d => d.Count)
.Take(10)
.Select(d => new
{
Name = d.Name,
Count = d.Count
})
.Concat(context.Data
.OrderByDescending(d => d.Count)
.Skip(10)
.Select(d => new
{
Name = "Others",
Count = d.Count
}))
.GroupBy(x => x.Name)
.Select(g => new downModel
{
modelName = g.Key,
count = g.Sum(x => x.Count)
})
.ToList();
If you want to create just one model, then get the sum first and create your object:
var count = Data.OrderBy(o => o.count).Skip(10).Sum(x => x.count);
var model = new downModel { modelname = "Others", count = count };
Btw, OrderBy performs a sort in ascending order. If you want to get (or Skip) top results you need to use OrderByDescending.

LINQ query has me baffled

How would I write a LINQ query to do the following?
I have a database table with a schema like this:
ID - Int
Time - DateTime
RecordType - Int
Msg - String
I want to get the newest (using 'Time' field) record for each 'RecordType'
Another restriction is that I'm only interested in certain RecordTypes - those contained in an int array.
The result of the query would be one record per RecordType - the newest record for this type.
var results = source.GroupBy(x => x.RecordType)
.Where(g => myRecordTypes.Contains(g.Key))
.Select(g => g.OrderByDescending(x => x.Time).First())
.ToList();
myRecordTypes is int[] with a set of RecordTypes you'd like to get as a result.
result will be List<Record> with one item per RecordType.
You can change to it to be e.g. Dictionary<int, Recort> by RecordType:
var results = source.GroupBy(x => x.RecordType)
.Where(g => myRecordTypes.Contains(g.Key))
.Select(g => new { g.Key, item = g.OrderByDescending(x => x.Time).First() })
.ToDictionary(x => x.Key, x => x.item);
Group them by record types, filter out the ones you want, and then select out the first of the items in that group ordered by time.
int[] recordTypes = GetRecordTypes();
var query = context.Table.GroupBy(item => item.RecordType)
.Where(group => recordTypes.Contains(group.Key))
.Select(group => group.OrderBy(item => item.Time).FirstOrDefault());

How do I .OrderBy() and .Take(x) this LINQ query?

The LINQ query below is working fine but I need to tweak it a bit.
I want all the records in the file grouped by recordId (a customer number) and then ordered by, in descending order, the date. I'm getting the grouping and the dates are in descending order. Now, here comes the tweaking.
I want the groups to be sorted, in ascending order, by recordId. Currently, the groups are sorted by the date, or so it seems. I tried adding a .OrderBy after the .GroupBy and couldn't get that to work at all.
Last, I want to .take(x) records where x is dependent on some other factors. Basically, the .take(x) will return the most-recent x records. I tried placing a .take(x) in various places and I wasn't getting the correct results.
var recipients = File.ReadAllLines(path)
.Select (record => record.Split('|'))
.Select (tokens => new
{
FirstName = tokens[2],
LastName = tokens[4],
recordId = tokens[13],
date = Convert.ToDateTime(tokens[17])
}
)
.OrderByDescending (m => m.date)
.GroupBy (m => m.recordId)
.Dump();
Edit #1 -
recordId is not unique. There may / will likely be multiple records with the same recordId. recordId is actually a customer number.
The output will be a resultset with first name, last name, date, and recordId. Depending on several factors, there many be 1 to 5 records returned for each recordId.
Edit #2 -
The .Take(x) is for the recordId. Each recordId may have multiple rows. For now, let's assume I want the most recent date for each recordId. (select top(1) when sorted by date descending)
Edit #3 -
The following query generates the following results. Note each recordId only produces 1 row in the output (this is okay) and it appears it is the most recent date. I haven't thouroughly checked this yet.
Now, how do I sort, in ascending order, by recordId?
var recipients = File.ReadAllLines(path)
.Select (record => record.Split('|'))
.Select (tokens => new
{
FirstName = tokens[2],
LastName = tokens[4],
recordId = Convert.ToInt32(tokens[13]),
date = Convert.ToDateTime(tokens[17])
}
)
.GroupBy (m => m.recordId)
.OrderByDescending (m => m.Max (x => x.date ) )
.Select (m => m.First () )
.Dump();
FirstName LastName recordId date
X X 2531334 3/11/2011 12:00:00 AM
X X 1443809 10/18/2001 12:00:00 AM
X X 2570897 3/10/2011 12:00:00 AM
X X 1960526 3/10/2011 12:00:00 AM
X X 2475293 3/10/2011 12:00:00 AM
X X 2601783 3/10/2011 12:00:00 AM
X X 2581844 3/6/2011 12:00:00 AM
X X 1773430 3/3/2011 12:00:00 AM
X X 1723271 2/4/2003 12:00:00 AM
X X 1341886 2/28/2011 12:00:00 AM
X X 1427818 11/15/1986 12:00:00 AM
You can't that easily order by a field which is not part of the group by fields. You get a list for each group. This means, you get a list of date for each recordId.
You could order by Max(date) or Min(date).
Or you could group by recordId and date, and order by date.
order by most recent date:
.GroupBy (m => m.recordId)
// take the most recent date in the group
.OrderByDescending (m => m.Max(x => x.date))
.SelectMany(x => x.First
The Take part is another question. You could just add Take(x) to the expression, then you get this number of groups.
Edit:
For a kind of select top(1):
.GroupBy (m => m.recordId)
// take the most recent date in the group
.OrderByDescending (m => m.Max(x => x.date))
// take the first of each group, which is the most recent
.Select(x => x.First())
// you got the most recent record of each recordId
// and you can take a certain number of it.
.Take(x);
snipped I had before in my answer, you won't need it according to your question as it is now:
// create a separate group for each unique date and recordId
.GroupBy (m => m.date, m => m.recordId)
.OrderByDescending (m => m.Key)
This seems very similar to your other question - Reading a delimted file using LINQ
I don't believe you want to use Group here at all - I believe instead that you want to use OrderBy and ThenBy - something like:
var recipients = File.ReadAllLines(path)
.Select (record => record.Split('|'))
.Select (tokens => new
{
FirstName = tokens[2],
LastName = tokens[4],
recordId = tokens[13],
date = Convert.ToDateTime(tokens[17])
}
)
.OrderBy (m => m.recordId)
.ThenByDescending (m => m.date)
.Dump();
For a simple Take... you can just add this .Take(N) just before the Dump()
However, I'm not sure this is what you are looking for? Can you clarify your question?
just add
.OrderBy( g=> g.Key);
after your grouping. This will order your groupings by RecordId ascending.
Last, I want to .take(x) records where
x is dependent on some other factors.
Basically, the .take(x) will return
the most-recent x records.
If you mean by "the most recent" by date, why would you want to group by RecordId in the first place - just order by date descending:
..
.OrderByDescending (m => m.date)
.Take(x)
.Dump();
If you just want to get the top x records in the order established by the grouping though you could do the following:
...
.GroupBy (m => m.recordId)
.SelectMany(s => s)
.Take(x)
.Dump();
If you want something like the first 3 for each group, then I think you need to use a nested query like:
var recipients = File.ReadAllLines(path)
.Select(record => record.Split('|'))
.Select(tokens => new
{
FirstName = tokens[2],
LastName = tokens[4],
RecordId = tokens[13],
Date = Convert.ToDateTime(tokens[17])
}
)
.GroupBy(m => m.RecordId)
.Select(grouped => new
{
Id = grouped.Key,
First3 = grouped.OrderByDescending(x => x.Date).Take(3)
}
.Dump();
and if you want this flattened into a record list then you can use SelectMany:
var recipients = var recipients = File.ReadAllLines(path)
.Select(record => record.Split('|'))
.Select(tokens => new
{
FirstName = tokens[2],
LastName = tokens[4],
RecordId = tokens[13],
Date = Convert.ToDateTime(tokens[17])
}
)
.GroupBy(m => m.RecordId)
.Select(grouped => grouped.OrderByDescending(x => x.Date).Take(3))
.SelectMany(item => item)
.Dump();

Categories

Resources