Select the Object with linq that best fit the Query strings - c#

I have a situation where I am trying to link two different data source together. see the code below:
static void Main(string[] args)
{
string url = System.Net.WebUtility.UrlDecode("yql");
HttpClient cl = new HttpClient();
var response = cl.GetStringAsync(url).Result;
JObject json = JObject.Parse(response);
var ids = from p in json["p"]
from span in p["span"]
where (string)span["content"] == "01/19/2017"
select (string)span["genre"];
}
this is what the JSON looks likeenter code here
please note this JSON is a little different from what you will get from yql as I shorten it removing bits I don't need. this is the
link to the full JSON - https://query.yahooapis.com/v1/public/yql?q=SELECT%20*%20FROM%20html%20WHERE%20url%3D%22http%3A%2F%2Fthemoviedb.org%2Fsearch%2Fmovie%3Fquery%3Dsplit%22%20%20AND%20xpath%3D'%2F%2Fdiv%5B%40class%3D%22info%22%5D'&format=json&callback=
I want to be able to query the JSON and select the object that best match this:
string year = "2017";
string genre = "Drama, Thriller, Action";
string title = "split"
or
public class Movie{
public string year {get; set;}
public string title {get; set;}
public Genre genre{get; set;}
}
then
List<Movie> _movie = new List<Movie>()
_movie.add(new Movie{year = 2017, title=split, genre[]=Thriller, Drama, Action})
from the query object and the JSON that from yql... the result should be split 01/19/2017

public class Movie
{
public string Year { get; set; }
public string Title { get; set; }
public string[] Genre { get; set; }
}
class Program
{
static void Main(string[] args)
{
string url = System.Net.WebUtility.UrlDecode("https://query.yahooapis.com/v1/public/yql?q=SELECT%20*%20FROM%20html%20WHERE%20url%3D%22http%3A%2F%2Fthemoviedb.org%2Fsearch%2Fmovie%3Fquery%3Dsplit%22%20%20AND%20xpath%3D%27%2F%2Fdiv%5B%40class%3D%22info%22%5D%27&format=json&callback=");
HttpClient cl = new HttpClient();
var response = cl.GetStringAsync(url).Result;
JObject json = JObject.Parse(response);
var movies = new List<Movie>();
foreach (var pchild in json["query"]["results"]["div"])
{
// title
var title = pchild["p"][0]["a"]["title"];
var titleStr = title != null ? title.Value<string>() : string.Empty;
// year
var releaseDate = pchild["p"][1]["span"][0]["content"];
string releaseYear = string.Empty;
DateTime temp;
if (releaseDate != null
&& DateTime.TryParse(releaseDate.Value<string>(), System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.None, out temp))
{
releaseYear = temp.Year.ToString();
}
// genres
var genre = pchild["p"][1]["span"][1]["content"];
var genreArr = genre != null
? genre.Value<string>()
.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries)
.Select(st => st.Trim())
.ToArray()
: new string[0];
movies.Add(
new Movie
{
Title = titleStr,
Year = releaseYear,
Genre = genreArr
});
}
// searching for the best match
string titleFilter = "Split";
string yearFilter = "2017";
var genreFilter = new string[] { "Drama", "Thriller", "Action" };
var bestMatches = movies
.OrderByDescending(m => m.Title == titleFilter)
.ThenByDescending(m => m.Year == yearFilter)
.ThenByDescending(m => m.Genre.Intersect(genreFilter).Count());
// the best match
var bestMatch = bestMatches.First();
Console.WriteLine(bestMatch.Title);
Console.WriteLine(bestMatch.Year);
Console.WriteLine(string.Join(",", bestMatch.Genre));
// all the movies already ordered
//foreach (var movie in bestMatches)
//{
// Console.WriteLine(movie.Title);
// Console.WriteLine(string.Join(",", movie.Genre));
// Console.WriteLine(movie.Year);
// Console.WriteLine();
//}
Console.ReadLine();
}
Input:
string titleFilter = "Split";
string yearFilter = "2017";
var genreFilter = new string[] { "Drama", "Thriller", "Action" };
Best match:
Split
2017
Drama,Horror,Thriller
Note that you may have several movies with the same matches.
Input:
string titleFilter = "Split";
string yearFilter = "2016";
var genreFilter = new string[] { "Drama", "Thriller", "Action" };
Best matches (you can uncomment the last part of code to see all the movies ordered):
Split
Fantasy,Drama
2016
Split
Drama
2016

Related

Convert List<string> to List<object> in C#

I have a list of string that looks something like this:
var rawData = new List<string>(){ "EUR/USD;123" , "EUR/GBP;321", "BTC/USD;2321"};
I have the following structure:
public class Data
{
public string instrument { get; set; }
public string positions { get; set; }
}
My goal is to have a list of the string data, splited on the ';' char and converted to a list of objects.
var processedData = new List<Data>();
// processedData[0] ( instrument = EUR/USD, positions = 123 )
// processedData[1] ( instrument = EUR/GBP, positions = 321)
// processedData[2] ( instrument = BTC/USD, positions = 2321)
Do you have any idea how can I do this ?
You can try Linq and query rawData:
var processedData = rawData
.Select(item => item.Split(';'))
.Select(items => new Data() {
instrument = items[0],
positions = items[1]
})
.ToList();
foreach(var rawString in rawData){
var split = rawString.Split(";");
processedData.Add(new Data(){
instruments = split[0],
positions = split[1]
});
}
You can try this code below
private static void TestFunc()
{
var rawData = new List<string>() { "EUR/USD;123", "EUR/GBP;321", "BTC/USD;2321" };
var processedData = new List<Data1>();
foreach (var item in rawData)
{
var ins = item.Split(";")[0];
var pos = item.Split(";")[1];
processedData.Add(new Data1(ins, pos));
}
}
you can use linq
void Main()
{
var rawData = new List<string>() { "EUR/USD;123", "EUR/GBP;321", "BTC/USD;2321" };
rawData.Select(s =>
new Data() {instrument= s.Split(";")[0],positions = (s.Split(";").Count() > 1) ? s.Split(";")[1] : null})
.ToList().Dump();
}
public class Data
{
public string instrument { get; set; }
public string positions { get; set; }
}

Regular Expression Not Equal is not working in C# string array

I researched this and cannot figure out why ?! doesn't do a Not Equal in my code.
The code has this :
var policyOne = "C";
var policyTwo = "GF1";
var policyThree = "018"
string policyNumber = $"^(?!{Regex.Escape(policyOne)}){Regex.Escape(policyTwo)}{Regex.Escape(policyTwo)}$",
So while I have also tried ?!.* , I still cannot get it to recognize that the policyOne is NOT allowed to be "C"
All 3 of the variables are joined together in a sql linq where clause
I can provide more details if needed.
This is my code
string AnyStart = "XXXDEFGHI";
string AnythingMiddle = "ABCXXXGHI";
string AnyEnds = "ABCDEFZZZ";
List<string> Strings = new List<string>()
{
AnythingMiddle,
AnyStart,
AnyEnds
};
string hcdpPlnCvgCD = "ABC";
string HcdpPmtFctrCd = "DEF";
string hccCpmtFctrCd = "GHI";
var patterns = new string[]{
$"^(?!{Regex.Escape(hcdpPlnCvgCD)}){Regex.Escape(HcdpPmtFctrCd)}{Regex.Escape(hccCpmtFctrCd)}$",
$"^{Regex.Escape(hcdpPlnCvgCD)}(?!.*{Regex.Escape(HcdpPmtFctrCd)}){Regex.Escape(hccCpmtFctrCd)}$",
$"^{Regex.Escape(hcdpPlnCvgCD)}{Regex.Escape(HcdpPmtFctrCd)}(?!.*{Regex.Escape(hccCpmtFctrCd)})$",
};
var wildcards = new List<string>();
foreach (var pattern in patterns)
{
var matchResult = Strings.Where(s => Regex.IsMatch(s, pattern)).ToList();
matchResult.Dump();
}
wildcards.Dump();
I'm going to suggest a few things, first you should be evaluating the 3 different patterns individually. Second it seems like a plan has some logic behind it so I propose a Plan class. Then you can use Linq to find the plans you want.
public class Plan
{
public Plan(string planCode)
{
PlanCode = planCode;
Evaluate();
}
private const string _planCoverage= "^ABC";
private const string _planPaymentFactor= "DEF";
private const string _planCoPaymentFactor = "GHI$";
public string PlanCode { get; set; }
public bool IsCoverage { get; set; }
public bool IsPayment { get; set; }
public bool IsCoPay { get; set; }
private void Evaluate()
{
IsCoverage = Regex.IsMatch(PlanCode, _planCoverage);
IsPayment = Regex.IsMatch(PlanCode, _planPaymentFactor);
IsCoPay = Regex.IsMatch(PlanCode, _planCoPaymentFactor);
}
}
using this class the following code should accomplish what you're trying to do
string anyStart = "XXXDEFGHI";
string anyMiddle = "ABCXXXGHI";
string anyEnd = "ABCDEFZZZ";
List<Plan> plans = new List<Plan>(){
new Plan(anyStart),
new Plan(anyMiddle),
new Plan(anyEnd)
};
//what your first regex tried to accomplish
List<string> noCoveragePlans = plans
.Where(plan => !plan.IsCoverage && plan.IsPayment && plan.IsCoPay)
.Select(plan => plan.PlanCode)
.ToList();
//what your second regex tried to accomplish
List<string> noPaymentPlans = plans
.Where(plan => plan.IsCoverage && !plan.IsPayment && plan.IsCoPay)
.Select(plan => plan.PlanCode)
.ToList();
//what your third regex tried to accomplish
List<string> noCoPayPlans = plans
.Where(plan => plan.IsCoverage && plan.IsPayment && !plan.IsCoPay)
.Select(plan => plan.PlanCode)
.ToList();

Using Dictionary Parameters as Optional Arguments when Calling Method

I'm presently trying to use a dictionary values to name optional parameters when invoking a method. I'm not sure this is possible with c# but I do something similar with queries using dynamic SQL.
string[] dobArrayKey = {"dob: "};
string[] dobArrayValue = {txtDob.Text};
string[] ptntNumArrayKey = { "PatientID: " };
string[] ptntNumArrayValue = { txtOfficeMR.Text};
string[] nameArrayKey = { "FirstName: ", "LastName: " };
string[] nameArrayValue = { txtFirstname.Text, txtLastname.Text };
List<List<string>> searchResults = new List<List<string>>();
Dictionary<string[], string[]> searchCriteria = new Dictionary<string[], string[]>
{
{dobArrayKey,dobArrayValue}
,{ptntNumArrayKey,ptntNumArrayValue}
,{nameArrayKey,nameArrayValue}
};
foreach (var item in searchCriteria)
{
if (item.Value[0] != "" && item.Value[0] != null)
{
searchResults.Add(new List<string>());
for (int x = 0; x <= item.Key.Count(); x++)
{
string strJSON = doPatientSearch(Convert.ToInt32(au.UserID)
, Convert.ToInt32(Session["PracticeID"]), au.SessionID, item.Key[x].ToString() : item.Value[x].ToString() );
PatientSearchResponse ptLi = JsonConvert.DeserializeObject<PatientSearchResponse>(json2);
foreach (PatientList3 patient in ptLi.PatientList)
{
searchResults[x].Add(patient.PatientNumber);
}
}
}
}
public static string doPatientSearch(int UserID, int PracticeID, string SessionID, string PatientID = null,
,string first = null, string last = null, string dob = null, string social = null)
{
//search
}
My colleague suggested I change the method itself by removing all of the optional parameters and instead passing through a dictionary that contains all of the parameters and handling them inside the method.
I think that would work, but for curiosities sake I wanted to get some feedback and find out whether or not something like I'm attempting to do in the above code is possible.
If it is impossible but there is another way of achieving the desired outcome I'd love to see your suggestions.
Thank you in advance.
Pass an expression
Since the criteria are used post-hoc (i.e. by filtering a complete resultset), you can use LINQ to filter the results. For maximum flexibility, the caller can pass in an Expression to be used as a callback on each item to determine if it should be included.
To get a filtered resultset:
public IEnumerable<Patient> FindPatients(Func<Patient,bool> criteria)
{
return sourceData
.Where (criteria);
}
To return a single result:
public Patient FindPatient(Func<Patient,bool> criteria)
{
return sourceData
.Single(criteria);
}
The criteria expression is just a function that accepts a patient and returns a Boolean. The caller can write this any way desired, or insert it as a lambda expression.
var results = patients.FindPatients( p => p.LastName == "Doe" );
Or
var results = patients.FindPatients
(
p =>
p.LastName.Contains("Doe") &&
p.PracticeID == 12
);
Or
var singleResult = patients.FindPatient( p => p.UserID == 1);
As you can see, the caller can provide literally any criteria desired, and has the benefit of type safety and early binding. This is far superior to using a Dictionary which has neither.
Full example code:
class Patient
{
public int UserID { get; set; }
public int PracticeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DOB { get; set; }
public string Social { get; set; }
public override string ToString()
{
return string.Format("{0} {1} {2}", UserID, FirstName, LastName);
}
}
class PatientRepository
{
static private readonly List<Patient> sourceData = new List<Patient>
{
new Patient
{
UserID = 1, PracticeID = 10, FirstName = "John", LastName = "Doe", DOB = DateTime.Parse("1/2/1968"), Social="123456789"
},
new Patient
{
UserID = 2, PracticeID = 10, FirstName = "Jane", LastName = "Doe", DOB = DateTime.Parse("1/2/1958"), Social="123456790"
},
new Patient
{
UserID = 3, PracticeID = 10, FirstName = "John", LastName = "Carson", DOB = DateTime.Parse("4/1/1938"), Social="123456791"
}
};
public IEnumerable<Patient> FindPatients(Func<Patient,bool> criteria)
{
return sourceData
.Where (criteria);
}
public Patient FindPatient(Func<Patient,bool> criteria)
{
return sourceData
.Single(criteria);
}
}
public class Program
{
public static void Main()
{
//Get a reference to the data store
var patients = new PatientRepository();
Console.WriteLine("Multiple record search");
var results = patients.FindPatients
(
p => p.LastName == "Doe"
);
foreach (var p in results)
{
Console.WriteLine(p);
}
Console.WriteLine("Single record search");
var singleResult = patients.FindPatient
(
p => p.UserID == 1
);
Console.WriteLine(singleResult);
}
}
Output:
Multiple record search
1 John Doe
2 Jane Doe
Single record search
1 John Doe
See the working code on DotNetFiddle

How to concatenate values in to a comma seperated values in c#

I am reading text file where I would like to get values based on condition. First I will take FREQ where CELLID = 639 and ISMAINBCCH=YES,that I have done now next task is I have to concatenate FREQ values in a comma separated way where CELLID=639 and ISMAINBCCH=NO, so the output I want is 24,28,67. How to achieve that?
lines are
ADD GCELL:CELLID=639, CELLNAME="NR_0702_07021_G1_A", MCC="424", MNC="02", LAC=6112, CI=7021, NCC=6, BCC=0, EXTTP=Normal_cell, IUOTP=Concentric_cell, ENIUO=ON, DBFREQBCCHIUO=Extra, FLEXMAIO=OFF, CSVSP=3, CSDSP=5, PSHPSP=4, PSLPSVP=6, BSPBCCHBLKS=1, BSPAGBLKSRES=4, BSPRACHBLKS=1, TYPE=GSM900_DCS1800,
......................
.............
ADD GTRX:TRXID=0, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-0", FREQ=81, TRXNO=0, CELLID=639, IDTYPE=BYID, ISMAINBCCH=YES, ISTMPTRX=NO, GTRXGROUPID=2556;
ADD GTRX:TRXID=1, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-1", FREQ=24, TRXNO=1, CELLID=639, IDTYPE=BYID, ISMAINBCCH=NO, ISTMPTRX=NO, GTRXGROUPID=2556;
ADD GTRX:TRXID=5, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-2", FREQ=28, TRXNO=2, CELLID=639, IDTYPE=BYID, ISMAINBCCH=NO, ISTMPTRX=NO, GTRXGROUPID=2556;
ADD GTRX:TRXID=6, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-3", FREQ=67, TRXNO=3, CELLID=639, IDTYPE=BYID, ISMAINBCCH=NO, ISTMPTRX=NO, GTRXGROUPID=2556;
Update
I am getting values like shown below
using (StreamReader sr = File.OpenText(filename))
{
while ((s = sr.ReadLine()) != null)
{
if (s.Contains("ADD GCELL:"))
{
var gtrx = new Gtrx
{
CellId = int.Parse(PullValue(s, "CELLID")),
Freq = int.Parse(PullValue(s, "FREQ")),
TrxNo = int.Parse(PullValue(s, "TRXNO")),
IsMainBcch = PullValue(s, "ISMAINBCCH").ToUpper() == "YES",
TrxName = PullValue(s, "TRXNAME"),
};
}
}
UPDATE
I used facade concept, but now it is taking lot of time. I am not sure whether I am using any bad logic two times I am iterating text file one for getting regular values and other for getting concatenated values
private class Gtrx
{
public int Freq { get; set; }
public int TrxNo { get; set; }
public string TrxName { get; set; }
public int CellId { get; set; }
public bool IsMainBcch { get; set; }
}
private class Gcell
{
public int CellId { get; set; }
public string CellName { get; set; }
public string Mcc { get; set; }
public int Lac { get; set; }
public int Ci { get; set; }
}
private class GcellGtrx
{
public Gcell Gcell { get; set; }
public Gtrx Gtrx { get; set; }
}
using (var sr = new StringReader(data))
{
string line = sr.ReadLine();
while (line != null)
{
line = line.Trim();
if (line.StartsWith("ADD GCELL:"))
{
var gcell = new Gcell
{
CellId = int.Parse(PullValue(line, "CELLID")),
CellName = PullValue(line, "CELLNAME"),
Ci = int.Parse(PullValue(line, "CI")),
Lac = int.Parse(PullValue(line, "LAC")),
Mcc = PullValue(line, "MCC")
};
var gcellGtrx = new GcellGtrx();
gcellGtrx.Gcell = gcell;
_dictionary.Add(gcell.CellId, gcellGtrx);
}
if (line.StartsWith("ADD GTRX:"))
{
var gtrx = new Gtrx
{
CellId = int.Parse(PullValue(line, "CELLID")),
Freq = int.Parse(PullValue(line, "FREQ")),
TrxNo = int.Parse(PullValue(line, "TRXNO")),
IsMainBcch = PullValue(line, "ISMAINBCCH").ToUpper() == "YES",
DEFINED_TCH_FRQ = null,
TrxName = PullValue(line, "TRXNAME")
};
if (!intarr.Contains(gtrx.CellId))
{
if (!_dictionary.ContainsKey(gtrx.CellId))
{
// No GCell record for this id. Do something!
continue;
}
intarr.Add(gtrx.CellId);
string results = string.Empty;
var result = String.Join(",",
from ss in File.ReadLines(filename)
where ss.Contains("ADD GTRX:")
where int.Parse(PullValue(ss, "CELLID")) == gtrx.CellId
where PullValue(ss, "ISMAINBCCH").ToUpper() != "YES"
select int.Parse(PullValue(ss, "FREQ")));
results = result;
var gtrxnew = new Gtrx
{
DEFINED_TCH_FRQ = results
};
_dictionary[gtrx.CellId].Gtrx = gtrx;
}
line = sr.ReadLine();
}
}
UPDATE
Finally I did it like first I saved lines starting with ADD GTRX in to an array by using File. Readalllines and then used only that array to get concatenated string instead of storing entire text file and got some performance improvement.
If I convert my Text files that contain hundreds of thousands of lines each into xml and then retrieve data from xml file rather from text file, will it make any performance improvement? If I use datatable and dataset rather than classes here will be a performance improvement?
Create gtrxs collection to store Gtrx objects and read data from file in to gtrxs. Then you can use LINQ (ming need to add using System.Linq;) to find Gtrx objects that are matching your requirements and call Select to get list of Freq values. Once you have a list, you can simply use String.Join(",", freqValues) to build up CSV string.
var gtrxs = new List<Gtrx>();
using (StreamReader sr = File.OpenText(filename))
{
while ((s = sr.ReadLine()) != null)
{
if (s.Contains("ADD GCELL:"))
{
var gtrx = new Gtrx
{
CellId = int.Parse(PullValue(s, "CELLID")),
Freq = int.Parse(PullValue(s, "FREQ")),
TrxNo = int.Parse(PullValue(s, "TRXNO")),
IsMainBcch = PullValue(s, "ISMAINBCCH").ToUpper() == "YES",
TrxName = PullValue(s, "TRXNAME"),
};
gtrxs.Add(gtrx);
}
}
}
IEnumerable<int> freqValues = gtrxs.Where(x => x.CellId == 639 && x.IsMainBcch == false).Select(x => x.Freq);
string result = String.Join(",", freqValues);
Does this work for you?
var result = String.Join(",",
from s in File.ReadAllLines(filename)
where s.Contains("ADD GCELL:")
where int.Parse(PullValue(s, "CELLID")) == 639
where PullValue(s, "ISMAINBCCH").ToUpper() != "YES"
select int.Parse(PullValue(s, "FREQ")));

Query or algorithm to generate a structure to populate a tree (C#, LINQ)

I couldn't find or come up with a generic and elegant algorithm that would let me populate the tree-like structure.
The simplest example is a blog archive: I have a bunch of records which can be selected and sorted by date.
I want to have a tree where years may be top level, months are next level and actual post titles are next again.
So far I've came up with a naive straight forward implementation that works, but I'm sure that could be improved using LINQ, etc.
Here I just sort records by date and iterate checking if the year or month has changed, and add tree nodes accordingly.
"BlogEntry" is a class that has a reference to both a parent and children and is later used to generate HTML.
I welcome suggestions on improving the algorithm!
IEnumerable<Post> posts = db.Posts.OrderBy(p => p.DateCreated);
var topPost = posts.First();
int curYear = topPost.DateCreated.Year;
int curMonth = topPost.DateCreated.Month;
//create first "year-level" item
var topYear = new BlogEntry { Name = topPost.DateCreated.Year.ToString().ToLink(string.Empty) };
entries.Add(topYear);
var currentYear = topYear;
var topMonth = new BlogEntry { Name = topPost.DateCreated.ToString("MMMM").ToLink(string.Empty), Parent = currentYear };
currentYear.Children.Add(topMonth);
var currentMonth = topMonth;
foreach (var post in posts)
{
if(post.DateCreated.Year == curYear)
{
if (post.DateCreated.Month != curMonth)
{
//create "month-level" item
var month = new BlogEntry { Name = post.DateCreated.ToString("MMMM").ToLink(string.Empty), Parent = currentYear };
currentYear.Children.Add(month);
currentMonth = month;
curMonth = post.DateCreated.Month;
}
//create "blog entry level" item
var blogEntry = new BlogEntry { Name = post.Title.ToLink("/Post/" + post.PostID + "/" + post.Title.ToSeoUrl() ), Parent = currentMonth };
currentMonth.Children.Add(blogEntry);
}
else
{
//create "year-level" item
var year = new BlogEntry { Name = post.DateCreated.Year.ToString().ToLink(string.Empty) };
entries.Add(year);
currentYear = year;
curMonth = post.DateCreated.Month;
curYear = post.DateCreated.Year;
}
}
I've created a test example, to check the correctness of the logic. I think this is what you need.
public class BlogEntyTreeItem
{
public string Text { set; get; }
public string URL { set; get; }
public List<BlogEntyTreeItem> Children { set; get; }
public List<BlogEntyTreeItem> GetTree()
{
NWDataContext db = new NWDataContext();
var p = db.Posts.ToList();
var list = p.GroupBy(g => g.DateCreated.Year).Select(g => new BlogEntyTreeItem
{
Text = g.Key.ToString(),
Children = g.GroupBy(g1 => g1.DateCreated.ToString("MMMM")).Select(g1 => new BlogEntyTreeItem
{
Text = g1.Key,
Children = g1.Select(i => new BlogEntyTreeItem { Text = i.Name }).ToList()
}).ToList()
}).ToList();
return list;
}
}
using the link Playing with Linq grouping: GroupByMany ?
suggested in How can I hierarchically group data using LINQ?
I first refactored my code into
Solution 1
var results = from allPosts in db.Posts.OrderBy(p => p.DateCreated)
group allPosts by allPosts.DateCreated.Year into postsByYear
select new
{
postsByYear.Key,
SubGroups = from yearLevelPosts in postsByYear
group yearLevelPosts by yearLevelPosts.DateCreated.Month into postsByMonth
select new
{
postsByMonth.Key,
SubGroups = from monthLevelPosts in postsByMonth
group monthLevelPosts by monthLevelPosts.Title into post
select post
}
};
foreach (var yearPosts in results)
{
//create "year-level" item
var year = new BlogEntry { Name = yearPosts.Key.ToString().ToLink(string.Empty) };
entries.Add(year);
foreach (var monthPosts in yearPosts.SubGroups)
{
//create "month-level" item
var month = new BlogEntry { Name = new DateTime(2000, (int)monthPosts.Key, 1).ToString("MMMM").ToLink(string.Empty), Parent = year };
year.Children.Add(month);
foreach (var postEntry in monthPosts.SubGroups)
{
//create "blog entry level" item
var post = postEntry.First() as Post;
var blogEntry = new BlogEntry { Name = post.Title.ToLink("/Post/" + post.PostID + "/" + post.Title.ToSeoUrl()), Parent = month };
month.Children.Add(blogEntry);
}
}
}
And then into a more generic
Solution 2
var results = db.Posts.OrderBy(p => p.DateCreated).GroupByMany(p => p.DateCreated.Year, p => p.DateCreated.Month);
foreach (var yearPosts in results)
{
//create "year-level" item
var year = new BlogEntry { Name = yearPosts.Key.ToString().ToLink(string.Empty) };
entries.Add(year);
foreach (var monthPosts in yearPosts.SubGroups)
{
//create "month-level" item
var month = new BlogEntry { Name = new DateTime(2000, (int)monthPosts.Key, 1).ToString("MMMM").ToLink(string.Empty), Parent = year };
year.Children.Add(month);
foreach (var postEntry in monthPosts.Items)
{
//create "blog entry level" item
var post = postEntry as Post;
var blogEntry = new BlogEntry { Name = post.Title.ToLink("/Post/" + post.PostID + "/" + post.Title.ToSeoUrl()), Parent = month };
month.Children.Add(blogEntry);
}
}
}
................................................
public static class MyEnumerableExtensions
{
public static IEnumerable<GroupResult> GroupByMany<TElement>(
this IEnumerable<TElement> elements,
params Func<TElement, object>[] groupSelectors)
{
if (groupSelectors.Length > 0)
{
var selector = groupSelectors.First();
//reduce the list recursively until zero
var nextSelectors = groupSelectors.Skip(1).ToArray();
return
elements.GroupBy(selector).Select(
g => new GroupResult
{
Key = g.Key,
Items = g,
SubGroups = g.GroupByMany(nextSelectors)
});
}
else
return null;
}
}
public class GroupResult
{
public object Key { get; set; }
public IEnumerable Items { get; set; }
public IEnumerable<GroupResult> SubGroups { get; set; }
}

Categories

Resources