I have stuck on this problem for a week and no solution found.
I have a POCO like below:
public class Journal {
public int Id { get; set; }
public string AuthorName { get; set; }
public string Category { get; set; }
public DateTime CreatedAt { get; set; }
}
I want to know during a specific date span ( grouped by months or years ) the amount of journals count by a AuthorName or a Category.
After I send the queryed object to JSON serializer then generated JSON data like below ( just using JSON to demonstrate the data I want to get, how to serializer a object to a JSON is not my problem )
data: {
'201301': {
'Alex': 10,
'James': 20
},
'201302': {
'Alex': 1,
'Jessica': 9
}
}
OR
data: {
'2012': {
'C#': 230
'VB.NET': 120,
'LINQ': 97
},
'2013': {
'C#': 115
'VB.NET': 29,
'LINQ': 36
}
}
What I know is to write a LINQ query in "method way" like:
IQueryable<Journal> query = db.GroupBy(x=> new
{
Year = key.CreatedAt.Year,
Month = key.CreatedAt.Month
}, prj => prj.AuthorName)
.Select(data => new {
Key = data.Key.Year * 100 + data.Key.Month, // very ugly code, I know
Details = data.GroupBy(y => y).Select(z => new { z.Key, Count = z.Count() })
});
The conditions that grouped by months or years, AuthorName or Category will be passed by two string type method parameters. What I don't know is how to use "Magic String" parameters in a GroupBy() method . After some googling, it seems that I cannot group data by passing a magic string like "AuthorName". What I should to do is build a expression tree and pass it to the GroupBy() method.
Any solution or suggestion is appreciate.
Ooh, this looks like a fun problem :)
So first, let's set up our faux-source, since I don't have your DB handy:
Ok, so now we've got a set of data to work with, let's look at what we want...we want something with a "shape" like:
That has roughly the same functionality as (in pseudo code):
Let's dissect it one piece at a time. First, how the heck do we do this dynamically?
The compiler does the magic for us normally - what it does is define a new
Type
, and we can do the same:So what we've done here is define a custom, throwaway type that has one field for each name we pass in, which is the same type as the (either Property or Field) on the source type. Nice!
Now how do we give LINQ what it wants?
First, let's set up an "input" for the func we'll return:
We know we'll need to "new up" one of our new dynamic types...
And we'll need to initialize it with the values coming in from that parameter...
But what the heck are we going to use for
bindings
? Hmm...well, we want something that binds to the corresponding properties/fields in the source type, but remaps them to ourdynamicType
fields...Oof...nasty looking, but we're still not done - so we need to declare a return type for the
Func
we're creating via Expression trees...when in doubt, useobject
!And finally, we'll bind this to our "input parameter" via
Lambda
, making the whole stack:For ease of use, let's wrap the whole mess up as an extension method, so now we've got:
Now, to use it:
This solution lacks the flexibility of "nested statements", like "CreatedDate.Month", but with a bit of imagination, you could possibly extend this idea to work with any freeform query.