Splitting list of objects by DateTime property - c#

I'm building a booking system for cinemas(of course it's just small project for studies).
Here is my Showcase model:
public class ShowcaseModel
{
public string objectId { get; set; }
public string MovieId { get; set; }
public int Auditorium { get; set; }
public DateTime? StartDate { get; set; }
}
I want to display schedule in "per day" form. To achieve this i get all Showcases where DateTime is greater than today and put them into
List< ShowcaseModel >.
Now i don't know how to split this list(into separate lists) by day using StartDate property.
Is there any way to achieve this?
Thanks in advance.

You can use GroupBy method:
List<ShowcaseModel> list = new List<ShowcaseModel>();
//...
var gooupByDay = list.GroupBy(o=>o.StartDate.Value.Date);

I have used fixture (simply creates random instances of your class) to demonstrate how you can get a list of items per date.
var fixture = new Fixture();
IEnumerable<ShowcaseModel> showCaseModel = fixture.CreateMany<ShowcaseModel>();
IEnumerable<ShowcaseModel> futureShowCases = showCaseModel.Where(s => s.StartDate != null && s.StartDate > DateTime.Now);
// we know none of start dates are null
var groupedShowCases = futureShowCases.GroupBy(s => s.StartDate.Value.Date);
List<Tuple<DateTime, IEnumerable<ShowcaseModel>>> showCasesByDate = new List<Tuple<DateTime, IEnumerable<ShowcaseModel>>>();
foreach (var groupedShowCase in groupedShowCases)
{
var key = groupedShowCase.Key;
showCasesByDate.Add(Tuple.Create(key, groupedShowCase.ToList().AsEnumerable()));
}

Using Linq GroupBy()
var grouped = list.Where(f=>f.StartDate!= null)
.GroupBy(f => f.StartDate.Value.Date, b => b,
(k, g) => new { Date= k.Date,
Movies= g }).ToList();
Assuming list is a collection of your ShowCaseModel

Related

Searching for an item in a list that's nested inside another list based on a property value in C# using LINQ?

Is there a way to search for an item in a list that's nested inside another list based on a property value using LINQ?
Given the follow models below, for a given Order (variable customerOrder), I want to return the earliest order date (Date) where the Day is "Sunday".
models:
public class Order
{
public int Id { get; set; }
public List<OrderLine> OrderLines { get; set; }
}
public class OrderLine
{
public string Description { get; set; }
public List<OrderDate> OrderDates { get; set; }
}
public class OrderDate
{
public DateTime Date { get; set; }
public string Day { get; set; }
}
code:
var dates = new List<DateTime>();
foreach(var a in customerOrder.OrderLines)
{
var orderDate = a.OrderDates.Where(x => x.DateTypeId.Equals("Sunday")).FirstOrDefault();
dates.Add(orderDate.ActualDate);
}
dates.OrderBy(d => d.Date);
return dates.FirstOrDefault();
EDIT
More elegant query
You can use Linq to achieve your result.
Here is a query that would closely mimick your code.
customerOrder.OrderLines
.Select(ol => ol.OrderDates
.Where(x => x.Day.Equals("Sunday"))
.FirstOrDefault())
.Where(d => d != null)
.OrderBy(d => d.Date)
.FirstOrDefault();
which could be more elegantly rewritten as:
customerOrder.OrderLines
.SelectMany(ol => ol.OrderDates)
.OrderBy(d => d.Date)
.FirstOrDefault(d => d.Day == "Sunday");
Here is a Linqpad query with some test data and dump for you to try.
Simply copy and paste in Linqpad.
void Main()
{
var customerOrder = new Order
{
Id = 1,
OrderLines = Enumerable
.Range(0, 10)
.Select(i => new OrderLine
{
Description = $"Line Description {i}",
OrderDates = Enumerable.Range(0, 10)
.Select(j => new OrderDate
{
Date = DateTime.Now.AddDays(i+j),
Day = DateTime.Now.AddDays(i+j).DayOfWeek.ToString()
})
.ToList()
})
.ToList()
}
.Dump();
customerOrder.OrderLines
.SelectMany(ol => ol.OrderDates)
.OrderBy(d => d.Date)
.FirstOrDefault(d => d.Day == "Sunday")
.Dump();
}
// You can define other methods, fields, classes and namespaces here
public class Order
{
public int Id { get; set; }
public List<OrderLine> OrderLines { get; set; }
}
public class OrderLine
{
public string Description { get; set; }
public List<OrderDate> OrderDates { get; set; }
}
public class OrderDate
{
public DateTime Date { get; set; }
public string Day { get; set; }
}
On a side note the OrderDate class is not necessary. The DateTime type has a property DayOfWeek that you can use to test is a Date is a Sunday.
DayOfWeek is an enum so you can simply test MyDate.DayOfWeek == DayOfWeek.Sunday rather than relying on a string for that purpose.
First of all your code will not work as intended.
dates.OrderBy(d => d.Date); doesn't work: OrderBy returns an IEnumerable, it doesn't change the original collection. You should either use List.Sort` or do this:
dates = dates.OrderBy(d => d.Date).ToList()
Secondly, you use FirstOrDefault: it has an overload that accepts predicate to search with; so the Where call is not needed. In addition FirstOrDefault will return null if nothing found. If this is a possible scenario, you should consider checking whether orderDate is null:
var dates = new List<DateTime>();
foreach(var a in customerOrder.OrderLines)
{
var orderDate = a.OrderDates.FirstOrDefault(x => x.DateTypeId.Equals("Sunday"));
if (orderDate is {})
{
dates.Add(orderDate.ActualDate);
}
}
dates = dates.OrderBy(d => d.Date).ToList();
return dates.FirstOrDefault();
That should work fine. But it hard to guess what aspects of behavior of your code samples are intended and what are not. You ask about searching, but say nothing about OrderBy part. Could you clarify this part, please?
Answering the question, if by better you mean more compact way, you can go with something like this:
var result = customerOrder.OrderLines
.SelectMany(a => a.OrderDates)
.OrderBy(d => d.Date)
.FirstOrDefault(x => x.DateTypeId.Equals("Sunday"));
return result;
You shouldn't be bothered with better way now; firstly you should start with at least working way. I suggest you to learn how to do these things both using Linq and without using Linq.
Better is a bit subjective, but you can use the Enumerable.SelectMany extension method to flatten the OrderDate instances into one sequence.
Then you can use the Enumerable.Where extension method to filter the dates that are "Sunday".
Then you can use the Enumerable.Min extension method to get the minimum date.
All of this can be chained together into a single statement.
DateTime earliestSunday = customeOrder
.OrderLines
.SelectMany(ol => ol.OrderDates)
.Where(od => od.Day == "Sunday")
.Min(od => od.Date);

C# add anonymous list to typed list

I am a little new to C#, sorry if the question is rudimentary.
I have an anonymous list as result of the following:
var Totals = model.Payments.GroupBy(pm => pm.PaymentType)
.Select(t => new
{
PaymentType = t.Key,
Amount = t.Sum(pm => pm.Amount)
});
Returns Totals as anonymous list with two entries.
Cash 10000.00
EFT 8000.00
Now I need to add this list to a typed list in my viewmodel. The typed list looks like this
public class PaymentExportViewModel
{
public List<PaymentReportViewModel> Payments { get; set; }
public List<PaymentSubTotals> Subs { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public String VenueName { get; set;}
public PaymentExportViewModel()
{
Payments = new List<PaymentReportViewModel>();
Subs = new List<PaymentSubTotals>();
}
}
public class PaymentSubTotals
{
public string Type { get; set; }
public decimal Amount { get; set; }
}
So since I need to "convert" to typed I do this
PaymentSubTotals subs = new PaymentSubTotals();
foreach (var t in Totals)
{
subs.Type = t.PaymentType;
subs.Amount = t.Amount;
model.Subs.Add(subs);
}
The result is that model.subs now contains 2 entries but both are the same (last entry in my loop)
EFT 8000.00
EFT 8000.00
What am I missing in the model.Subs.Add ? Or somewhere else ?
Move declation of subs inside of foreach loop:
foreach (var t in Totals)
{
PaymentSubTotals subs = new PaymentSubTotals();
subs.Type = t.PaymentType;
subs.Amount = t.Amount;
model.Subs.Add(subs);
}
In your code you change the same sub object every time, so you do have only the data of the last t variable, repeated.
Why don't you Select into your PaymentSubTotals instead.
var Totals = model.Payments.GroupBy(pm => pm.PaymentType)
.Select(t => new PaymentSubTotals
{
Type = t.Key,
Amount = t.Sum(pm => pm.Amount)
});
I would recommend the following method to add a new PaymentSubTotals object on each loop.
foreach (var t in Totals)
{
model.Subs.Add(new PaymentSubTotals {
Type = t.PaymentType;
Amount = t.Amount;
});
}

Group IEnumerable into a string

I wonder if someone could spare me a few minutes to give me some advice please?
I've created an IEnumerable list:
public class EmailBlock
{
public int alertCategory { get; set; }
public string alertName { get; set; }
public string alertURL { get; set; }
public string alertSnippet { get; set; } //Need to work out the snippet
}
List<EmailBlock> myEmailData = new List<EmailBlock>();
Which I then loop through some data (Umbraco content - not that that's really relevant!) and add items to the list.
myEmailData.Add(new EmailBlock { alertCategory = category.Id, alertName = alert.GetPropertyValue("pageTitle"), alertURL = alert.NiceUrl });
What ultimately I'd like to do is group the list by the alertCategory and then load each 'group' (another loop occurs later to check what members have subscribed to what alert category) into a variable which I can then use as an email's content.
You could use Linq's GroupBy() to do this:
using System.Linq
...
//Create a type to hold your grouped emails
public class GroupedEmail
{
public int AlertCategory { get; set; }
public IEnumerable<EmailBlock> EmailsInGroup {get; set; }
}
var grouped = myEmailData
.GroupBy(e => e.alertCategory)
.Select(g => new GroupedEmail
{
AlertCategory = g.Key,
EmailsInGroup = g
});
You can select to an anonymous type if required and project your sequence into whatever structure you require.
Linq has a nice group by statement:
var emailGroup = emailList.GroupBy(e => e.alertCategory);
Then you can loop through each grouping and do whatever you want:
foreach(var grouping in emailGroup)
{
//do whatever you want here.
//note grouping will access the list of grouped items, grouping.Key will show the grouped by field
}
Edit:
To retrieve a group after you have grouped them, just use Where for more than one or First for just one:
var group = emailGroup.First(g => g.Key == "name you are looking for");
or
var groups = emailGroup.Where(g => listOfWantedKeys.Contains(g.Key));
this is a lot more efficient than looping through every time you need to find something.

Linq "join" with a IList<T> getting "Error Unable to create a constant value.."

I have a Save Method that saves with a Linq query a manually re-orderd list (in a web form) that is passed as the parameter to my method, and I try to update the Order Property of the IEnumerable<VM_CategoryLabel> I retrieve from the database (EF) with the corresponding value in the list (maybe would that be clearer with my code below):
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
//Order = x.Sequence_Cat,
Order = (int)listTemplate.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
I used the "test" var to see if my "sub-query" gets the correct value, and it does, but when I use my Linq expression inside the Select (the commented Order line), I get the following error:
Unable to create a constant value of type 'Namespace.Models.VM_CategoryLabelExtra. "Only primitive types and enumeration types are supported in this context.
Here are my classes:
public class VM_CategoryLabel
{
public int Id { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
public class VM_CategoryLabelExtra
{
public int Id { get; set; }
public int IdCat { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
So I suppose that I should not query the list inside my query ? So how do I "match" the 2 lists of values ?
I also tried the following (after having replace in the Linq query: Order = x.Sequence_Cat)that is not working neither because the iteration variable is
read-only:
foreach (var item in requete)
{
item.Order = listTemplate.Where(x => x.Id == item.Id).Select(x => x.Order).FirstOrDefault();
}
try
{
context.SaveChanges();
I suggest using this.
It is the let clause.
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
let list = listTemplate
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
Order = list.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
edit: instead offrom you can just do let list = listTemplate
Should work now :)
example for let:
// The let keyword in query expressions comes in useful with subqueries: it lets
// you re-use the subquery in the projection:
from c in Customers
let highValuePurchases = c.Purchases.Where (p => p.Price > 1000)
where highValuePurchases.Any()
select new
{
c.Name,
highValuePurchases
}
If you do not know how Let working than please download LinqPad and see an example

LINQ- Ordering a list of objects based on a particular value

I am new to LINQ. I have a class like this:
public class StudentResults
{
public int Id { get; set; }
public ResultsStatus StatusResult { get; set; }
public string Message { get; set; }
public StudentDetails Detail { get; set; }
}
There is a method that returns a List of the above class to a variable
I need to iterate thru that variable and put the students into two different classes.
PassedStudents, FailedStudents based on ResultsStatus.
Here is what I have tried but it doesnt work
var studIds = from r in StudList
group r by r.Id into GroupedData
select new
{
//what to put here
};
foreach(var crs in studIds)
{
//what to put here to get all student names from the Detail property.
}
Is there another way?
Personally, I would just treat this as two queries:
var passed = StudList.Where(student => student.StatusResult == ResultStatus.Passed);
var failed = StudList.Where(student => student.StatusResult == ResultStatus.Failed);
Console.WriteLine("Passed...");
foreach(var student in passed)
Console.WriteLine(student.Detail.Name);
Console.WriteLine("Failed...");
foreach(var student in failed)
Console.WriteLine(student.Detail.Name);
Sounds like you'd like 2 lists: one for failed, and one for passed.
Try this:
List<StudentResults> failed = StudList.Where(x=>x.ResultStatus=="Failed")
.ToList();
List<StudentResults> passed = StudList.Where(x=>x.ResultStatus=="Passed")
.ToList();
Simply use "where". For instance, to get all the "PassedStudents":
var passedStudents = from student in StudentList
where student.StatusResult == ResultsStatus.PassedStudent
select student;
foreach (var student in passedStudents)
{
Console.WriteLine("[{0}.] {1} passed.", student.Id, student.Detail.Name);
}
You can also write the query using lambda expressions:
var passedStudents =
StudentList.Where(student => student.StatusResult == ResultsStatus.PassedStudent);

Categories