Order by multiple lists of object - c#

I've three lists in an object and want to perform order by operation using LINQ
object containing lists
public class ApplicationCommunications
{
public ApplicationCommunications()
{
listNotification = new List<ApplicationNotifications>();
listEmail = new List<ApplicationEmail>();
listSMS = new List<ApplicationSMS>();
}
public List<ApplicationNotifications> listNotification { get; set; }
public List<ApplicationEmail> listEmail { get; set; }
public List<ApplicationSMS> listSMS { get; set; }
}
Getting data from db
ApplicationCommunications applicationCommunications = new ApplicationCommunications();
applicationCommunications.listNotification = GetApplicationNotification(applicationId).Select(c => new ApplicationNotifications
{
NotificationId = c.NotificationId,
Message = c.Message,
SendDate = c.SendDate.Value
}).ToList();
applicationCommunications.listEmail = GetApplicationEmails(applicationId).Select(t => new ApplicationEmail
{
EmailContent = t.Body,
EmailAddress = t.Email,
SendDate = t.SendDate.Value,
}).ToList();
applicationCommunications.listSMS = GetApplicationMessage(applicationId).Select(t => new ApplicationSMS
{
SMSContent = t.Body,
PhoneNumber = t.Phone,
SendDate = t.SendDate.Value,
}).ToList();
We've three lists each list of the object has "senddate" property now I want to make a new list from these three lists where we will have data in order. Is that possible?
How we can perform order by with send date? simply I want to display data in order.

Select method gives you Enumerable type of list. Enumerable can be ordered by OrderBy, so simply do this
applicationCommunications.listNotification = GetApplicationNotification(applicationId).Select(c => new ApplicationNotifications
{
NotificationId = c.NotificationId,
Message = c.Message,
NotificationSendDate = c.SendDate.Value
})
.OrderBy(an => an.NotificationSendDate)
.ThenBy(an => an.NotificationId)
.ToList();
EDIT:
You can read more here https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.orderby?view=net-5.0

pardon for the incomplete question. We've three lists each list of the
object has "senddate" property now I want to make a new list from
these three lists where we will have data in order. Is that possible?
As shown in the other answer you need OrderBy:
List<DateTime> orderedSendDates = applicationCommunications.listNotification
.Select(x => x.NotificationSendDate)
.Concat(applicationCommunications.listEmail.Select(x => x.EmailSendDate))
.Concat(applicationCommunications.listSMS.Select(x => x.SMSSendDate))
.OrderBy(dt => dt)
.ToList();
If you want unique DateTimes use Distinct before the OrderBy.
If you don't have these properties initialized when you want the list you could do:
List<DateTime> orderedSendDates =
GetApplicationNotification(applicationId).Select(x => x.SendDate)
.Concat(GetApplicationEmails(applicationId).Select(x => x.SendDate))
.Concat(GetApplicationMessage(applicationId).Select(x => x.SendDate))
.Where(sendDateOrNull => sendDateOrNull.HasValue)
.Select(sendDateOrNull => sendDateOrNull.Value)
.OrderBy(dt => dt)
.ToList();

If you want a big list containing the different types of elements, ordered by sendDate (but not just a list of dateTime), you may first create a common type for that :
public class SentElement {
public string ElementDescription {get ; set;}
public DateTime SendDate { get; set;}
}
Then map your different types to the common type using Select, filling the description the way you want for each type of element:
var listNotification = GetApplicationNotification(applicationId).Select(c => new SentElement
{
ElementDescription = c.NotificationId + c.Message,
SendDate= c.SendDate.Value
}).ToList();
var listEmail = GetApplicationEmails(applicationId).Select(t => new SentElement
{
ElementDescription = t.EmailContent + t.EmailAddress,
SendDate = t.SendDate.Value,
}).ToList();
var listSMS = GetApplicationMessage(applicationId).Select(t => new SentElement
{
ElementDescription = t.Body + t.Phone,
SendDate = t.SendDate.Value,
}).ToList();
And finally merging and ordering the result :
var mergedList = listNotification.Concat(listEmail).Concat(listSMS).OrderByDescending(t=> t.SendDate);

Related

Get common records from list of list in linq

I have a list contains another list where I supposed to get the common elements.
Class model:
PlanInfo has shiftName, ilist of Plan.
Plan has start time, end time
public class Plan
{
public int Start { get; set; }
public int End { get; set; }
}
public class PlanInfo
{
public string ShiftName { get; set; }
public IList<Plan> lstPlan { get; set; }
}
iList of PlanInfo contains
[“shift1”, [1000,1200]],
[“shift2”,[1000,1100]],
[“shift3”,[1000,1200]]
Expected output in this should be empty since 1000,1200 doesn’t exist in shift2
[“shift1”, [1000,1200]],
[“shift2”,[[1000,1200],[1000,1100]],
[“shift3”,[1000,1200]]
Should return [1000,1200] since it’s common in all lists.
I tried using intersect, but here IList<PlanInfo is not fixed length. it could have more than one records.
Kindly suggest me which LINQ query serve the above result
Hmm, If I understand the requirements: Given a list of PlanInfo, find any Plans common to all PlanInfo...
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.ShiftName, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
This assumes that a Plan can only be counted once within a PlanInfo. (No duplicate plans) This also assumes that the plan info references for the same start/end times are pointing to the same object instance. If not, then you cannot group on the Plan, you will need a unique key (Like a plan ID) to group on. If these are EF entities pulled from a DbContext then they will be the same reference.
First get the total # of plan infos. In your example this would return 3.
Next, for all plan infos, use SelectMany to fetch the Plans, but compose that down into the PlanInfo.ShiftName + the Plan. This flattens your one to many. Next group by the Plan so that we can count the # of PlanInfos that each Plan appears in. Any/all counts that match the total number of PlanInfos means a Plan that appears in all PlanInfos, Select the Key to get that grouped Plan(s) and that should have it.
Edit: adding an example...
[Test]
public void TestPlanCheck()
{
var plan1 = new Plan { Start = 1, End = 2 };
var plan2 = new Plan { Start = 2, End = 3 };
var plan3 = new Plan { Start = 3, End = 4 };
var planInfos = new List<PlanInfo>
{
new PlanInfo{ Name = "Test1", Plans = new []{ plan1, plan2}.ToList() },
new PlanInfo{Name = "Test2", Plans = new []{plan2, plan3}.ToList()},
new PlanInfo{Name = "Test3", Plans = new []{ plan3, plan2}.ToList() }
};
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.Name, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
}
private class Plan
{
public int Start { get; set; }
public int End { get; set; }
}
private class PlanInfo
{
public string Name { get; set; }
public List<Plan> Plans { get; set; }
}
That was the test I had run using these stub classes. In this case the test will return back 1 match, for the Plan 2 value.
To outline the issue with ensuring plan references for the same start/end times match: If the setup looked like this:
[Test]
public void TestPlanCheck()
{
var plan1 = new Plan { Start = 1, End = 2 };
var plan2A = new Plan { Start = 2, End = 3 };
var plan2B = new Plan { Start = 2, End = 3 };
var plan3 = new Plan { Start = 3, End = 4 };
var planInfos = new List<PlanInfo>
{
new PlanInfo{ Name = "Test1", Plans = new []{ plan1, plan2A}.ToList() },
new PlanInfo{Name = "Test2", Plans = new []{plan2B, plan3}.ToList()},
new PlanInfo{Name = "Test3", Plans = new []{ plan3, plan2B}.ToList() }
};
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.Name, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
}
In this case even though plan 2A and 2B have the same start/end time, the group by would not group them together because they represent 2 references to 2 objects. This though would be fine:
var plan2A = new Plan { Start = 2, End = 3 };
var plan2B = plan2A;
Both point to the same reference. If you do have different references for the same plan ranges, you would need a planID then group on a PlanId. Ideally though I would check why the references don't match because they should to avoid potential errors based on assumptions of equality.
One can use Aggregate with Intersect on PlanInfo.Plans like:
var plansCommon = planInfoList.Select(p => p.Plans)
.Aggregate<IEnumerable<Plan>>((p1, p2) =>
p1.Intersect(p2, new PlanComparer()))
.ToList();
// Implement IEqualityComparer
class PlanComparer : IEqualityComparer<Plan>
{
public bool Equals(Plan x, Plan y)
{
if (x.Start == y.Start &&
x.End == y.End)
return true;
return false;
}
public int GetHashCode(Plan obj)
{
return obj.Start.GetHashCode() ^ obj.End.GetHashCode();
}
}
The Intersect will recursively apply on Plans list of each PlanInfo to provide list of Plan common across all.

How to combine to generic lists with add range and select criteria?

How to combine to generic lists with add range and select criteria? Here is a fictitious example. I can addRange but not with filtering on a criteria like room type. The "Select(mr => mr.Type == RoomType.BedRoom)" does not work. What can I use instead to just append the list where type = BedRoom?
public enum RoomType
{
Bathroom = 1,
BedRoom = 2,
Kitchen = 3,
RecRoom = 4
}
public class RoomsModel
{
public RoomType Type { get; set; }
public int Size { get; set; }
}
public List<RoomsModel> GetRooms(params)
{
var result = new List<RoomsModel>();
result = _service.GetRooms(house1);
var moreRooms _service.GetRooms(house2);
result.AddRange((from mr in moreRooms
select new RoomsModel
{
Type = mr.Type,
Size = mr.Size
}
).Select(mr => mr.Type == RoomType.BedRoom).ToList());
return result;
}
Use Where instead of Select:
result.AddRange(
from mr in moreRooms
where mr.Type == RoomType.BedRoom
select new RoomsModel
{
Type = mr.Type,
Size = mr.Size
});
Where filters items. Select projects item, i.e. transforms each item in the sequence into something else.
You need to use Where instead of Select:
result.AddRange((from mr in moreRooms
select new RoomsModel
{
Type = mr.Type,
Size = mr.Size
}
).Where(mr => mr.Type == RoomType.BedRoom).ToList());
one more way:
result.AddRange(moreRooms.Where(mr => mr.Type == RoomType.BedRoom)
.Select(mr => new RoomsModel {
Type = mr.Type,
Size = mr.Size
}));

Separate class for the same purpose

I have this class
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<string> Blessings;
}
I am Getting the response of the two lists this way:
public async Task<List<BlessingDTO>> GetBlessing(string UserType)
{
string blessing = "Blessing_" + UserType;
List<BlessingDTO> results = new List<BlessingDTO>();
using (DTS_OnlineContext context = new DTS_OnlineContext())
{
var items = await context.Messages.AsNoTracking().Where(x => x.MessageContext == blessing).GroupBy(x=>x.GroupKey).Select(b=>b.OrderBy(x=>x.Sort)).ToListAsync();
if (items.Count() > 0)
{//Notes.Select(x => x.Author).Distinct();
results = items.ToList().ConvertAll(x => new BlessingDTO()
{ BlessingCategoryName = x.ToList().Select(y => y.MessageName).Distinct().ToList(),
Blessings = x.ToList().Select(y => y.MessageText).ToList()
});
}
}
return results;
}
if I am changing the class, for my porpuse to be:
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<bless> Blessings;
}
public class bless
{
public string text;
public int length;
}
how can I initialize the new class ?
Blessings = new bless
won't give the results. how can I save the data to bring them in the response
Let's focus in this part:
items
.ToList()
.ConvertAll(x =>
new BlessingDTO()
{
BlessingCategoryName = x.ToList().Select(y => y.MessageName).Distinct().ToList(),
Blessings = x.ToList().Select(y => y.MessageText).ToList()
}
);
where items is probably a List<List<Message>>, thus x being a List<Message>.
Now what is causing an error is the following: Blessings = x.ToList().Select(y => y.MessageText).ToList(). This creates a new list for the list of messages, then selects the MessageText from that list, which results in IEnumerable<string>. In the end a new list is created for these strings. This list of strings isn't assignable to List<bless>, thus will generate an error.
What you want is a result of List<bless>, so we need to convert the List<Message> list into a List<bless> somehow. We know how to do that, namely with a select: x.Select(message => new bless()).ToList(). All we have to do is fill in the properties of bless: x.Select(message => new bless { text = message.MessageText }).ToList(). The other property is up to you.
You can initialise the list like this:
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<bless> Blessings = new List<bless>();
}
Although, I would recommend these fields are changes to properties, as that is more idiomatic in C#
public class BlessingDTO
{
public List<string> BlessingCategoryName {get;set;}
public List<bless> Blessings {get;set;} = new List<bless>();
}

Populating SubModels using LINQ

I have a list of object which is something like this
[Code Type][condition Type][Question Type][Description]
[A1][C1][q1][dC1]
[A1][C1][q2][dC1]
[A1][C1][q3][dC1]
[B1][C2][q4][dC2]
[B1][C2][q5][dC2]
[B1][C2][q6][dC2]
[B1][C3][q7][dC3]
[B1][C3][q8][dC3]
[B1][C3][q9][dc3]
I want to map this with a class which has a subclass and it's subclass also has a subclass.
Structure is like this
public Class TypeModel
{
public string Type{get;set;}
public List<ConditionModel> Conditions {get;set;}
}
public Class ConditionModel
{
public string Type{get;set;}
public string Description {get;set;}
public List<QuestionModel> Questions {get;set;}
}
public Class QuestionModel
{
public string Type {get;set;}
}
I have written this LINQ query to populate the main Type Class so far but it is not working. I need help in creating this query.
var results = allTypes.GroupBy(type => type.CodeType)
.Select(grp => new TypeModel
{
Type = grp.Select(i => i.CodeType).First(),
Conditions = new List<ConditionModel>
{
grp.GroupBy(condition => condition.ConditionType)
.Select(conditionGrp => new ConditionModel {
Type = conditionGrp.Select(i => i.ConditionType).First(),
Description = conditionGrp.Select(i => i.Description).First(),
Questions = new List<QuestionModel>
{
conditionGrp.GroupBy(question => question.QuestionType)
.Select(questionGrp => new QuestionModel
{
Type = questionGrp.Select(i => i.QuestionType).First(),
})
}
})
}
});
What I am trying to achieve with this query? To get list of TypeModel.
If you'll notice the table first three rows will fetch me one typeModel and another 6 rows another typeModel but it will have two ConditonModels and each condition Model, 3 questionModel.
The following should work:
Group by CodeType first, then inside each group, group by ConditionType, Description and select appropriate results.
var results = allTypes.GroupBy(
type => type.CodeType, // CodeType key selector
(codeType, elements) => new TypeModel
{
Type = codeType,
Conditions = elements.GroupBy(
x => new { x.ConditionType, x.Description }, // ConditionType key selector
x => x.QuestionType, // QuestionType selector as elements of the ConditionType group
(condition, elements2) => new ConditionModel
{
Type = condition.ConditionType,
Description = condition.Description,
// Questions transformation
Questions = elements2.Select(q => new QuestionModel { Type = q }).ToList()
}).ToList()
});
In case you are confused by so much nested LINQ, there is nothing wrong in using some plain old loops in order to create your resulting data:
var results = new List<TypeModel>();
foreach (var item in allTypes.GroupBy(type => type.CodeType))
{
var conditionsList = new List<ConditionModel>();
foreach (var item2 in item.GroupBy(x => new { x.ConditionType, x.Description }))
{
conditionsList.Add(new ConditionModel
{
Type = item2.Key.ConditionType,
Description = item2.Key.Description,
Questions = item2.Select(x => new QuestionModel { Type = x.QuestionType }).ToList()
});
}
results.Add(new TypeModel
{
Type = item.Key,
Conditions = conditionsList
});
}

Issue displaying LINQ query results in a grid in C#

I received some help here with the following LINQ query, but am still struggling with it. The result I'm trying to obtain is to display some attributes and their values from an xml file in a DataGridView control. I'm calling my method from a button click and am trying to pass back the list for display in the grid. Here is an example of the row:
<z:row CenterCode="JAX" CenterName="Jacksonville" Version="1.0" NextExport="66742" NextImport="29756" LastImportTime="2015-06-10T14:48:33" FtpProxyServer="" FtpUserName="" FtpPassword="" ResetImportID="False"/>
Here is the method:
public static List<string[]> MonitorCounts(string upperLimit)
{
// Load xml
XDocument xmldoc = XDocument.Load(#"c:\XML\Configuration.xml");
XNamespace z = "#RowsetSchema";
Int32 limit = Convert.ToInt32(upperLimit);
var elementQuery = xmldoc.Descendants(z + "row").Where(e => (long?)e.Attribute("NextExport") > limit | (long?)e.Attribute("NextImport") > limit);
var attributes = elementQuery.Select(e => e.Attributes().Select(a => new KeyValuePair<string, string>(a.Name.LocalName, (string)a)).ToList()).ToList();
return attributes;
}
My questions are how to select only specific attributes and values in attributes. If I do something like this:
var attributes = elementQuery.Select(e => e.Attributes("CenterName").Select(a => new KeyValuePair<string, string>(a.Name.LocalName, (string)a)).ToList()).ToList();
then this is returned:
[0] = {[CenterName, Jacksonville]}
I need to select this and 4 others. I'm also getting a convrsion error - Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string,string>>>' to 'System.Collections.Generic.List<string[]>. Appreciate any pointers to help me along.
You can use an anonymous type:
var attributes =
elementQuery.Select(e => new
{
CenterName = (string)e.Attribute["CenterName"],
Version = (string)e.Attribute["Version"],
// more attributes
}).ToList();
You can't however return this from the method in a useful way. So if you really need both the attribute name and the attribute value as strings, try this approach instead:
var attributes =
elementQuery.Select(e => new []
{
Tuple.Create("CenterName", (string)e.Attribute["CenterName"]),
Tuple.Create("Version", (string)e.Attribute["Version"]),
// more attributes
}).SelectMany(x => x).ToList();
The return type of your method now has to be List<Tuple<string, string>>.
And finally, if you actually need a List<string[]> as the return type, use this code:
var attributes =
elementQuery.Select(e => new []
{
new [] { "CenterName", (string)e.Attribute["CenterName"] },
new [] { "Version", (string)e.Attribute["Version"] },
// more attributes
}).SelectMany(x => x).ToList();
I solved my own problem. Here is what I did:
Created a class for the attributes needed:
public class dataRow
{
public string CenterName { get; set; }
public string CenterCode { get; set; }
public string NextImport { get; set; }
public string NextExport { get; set; }
public string LastImportTime { get; set; }
}
Selected the results into it:
List<dataRow> dataRows = elementQuery.Select( e => new dataRow
{ CenterName = (string)e.Attribute("CenterName"),
CenterCode = (string)e.Attribute("CenterCode"),
NextImport = (string)e.Attribute("NextImport"),
NextExport = (string)e.Attribute("NextExport"),
LastImportTime = (string)e.Attribute("LastImportTime") }).ToList();
Changed my method to return the correct object:
public static List<dataRow> MonitorCounts(string upperLimit)
Set my grids datasource to the method return:
dataGridView1.DataSource = xmlProcessing.MonitorCounts(tbxUpperLimit.Text.ToString());
return dataRows;

Categories

Resources