I have a list of Example class elements:
public class Example
{
public long Id { get; set; }
public string Name { get; set; }
public DateTime SomeDate { get; set; }
}
Now I want to group it using ONE LINQ to make the following hierarchy:
public class GroupedByDay
{
public List<GroupedByTime> TimeGroup { get; set; }
}
public class GroupedByTime
{
public List<GroupedById> IdGroup { get; set; }
}
public class GroupedById
{
public string Name { get; set; }
}
So, the result is a list of type List<GroupedByDay> with the Examples grouped by days, hours (timespans?) within these days and finally by ids.
Can anyone help me with it?
[edit]
This is what I tried to group by Ids, but I think I should start from the other side maybe?
var result =
examples
.GroupBy(e => e.Id, e => new GroupedById
{
Name = e.Name
});
If you just want to group for displaying purposes, you don't need the classes GroupedByDay, GroupedByTime and GroupedById
Considering examples is an IEnumerable<Example>
var groupedExamples = from example in examples
group example by new {
example.SomeDate.Date, //Day
example.SomeDate.Hour, // Hour
example.Id // Id
} into g
select g;
Then you'll have an IEnumerable<IGrouping<,Example>> with the desired grouping:
foreach(var g in groupedExample){
Console.WriteLine(String.Format("Day {0} at hour {1} with id {2}", g.Key.Date, g.Key.Hour, g.Key.Id));
foreach(var example in g)
Console.WriteLine(" - " + example.Name);
}
I Usually write these code
public static DateTime GetDateByWeekDay(DateTime startDate, int week, Int32 day)
{
int Year = Getyear(startDate);
DateTime jan1 = new DateTime(Year, 1, 1);
int daysOffset = DayOfWeek.Monday - jan1.DayOfWeek;
DateTime firstMonday = jan1.AddDays(daysOffset);
var cal = CultureInfo.CurrentCulture.Calendar;
int firstWeek = cal.GetWeekOfYear(firstMonday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
var weekNum = week;
if (firstWeek <= 1)
{
weekNum -= 1;}
var result = firstMonday.AddDays(weekNum * 7);
return result.AddDays(day);
}
Related
I am trying to implement a recurrence pattern for my Calendar Application.
I want it to work the same way Outlook does when you set an appointment with reccurrence.
public async Task<ValidationResponse<ReccurrenceModel>> ApplyReccurrencePeriod (string userName, ReccurrenceModel value)
{
var user = await repository.FindByUserName(userName);
var fromDateUTC = DateTime.SpecifyKind(value.FromDate, DateTimeKind.Utc);
var toDateUTC = DateTime.SpecifyKind(value.ToDate, DateTimeKind.Utc);
var dates = new List<DateTime>();
var weeklyReccurrence = value.weeklyReccurrence;
if (value.IsMonday == true)
{
var fromDate = value.FromDate;
var toDate = value.ToDate;
for (var dt = fromDate; dt < toDate; dt = dt.AddDays(1))
{
dates.Add(dt);
}
var savedDates = dates.Where(x => x.DayOfWeek == DayOfWeek.Monday).Select(x => x.Date);
}
// I do the same code to verify every week day
var test = dates.Where(x => x.DayOfWeek == DayOfWeek.Friday).Select(x => x.Date);
}
foreach (var date in savedDates) {
var x = user.Holidays.FirstOrDefault(kvp => kvp.Key == date
&& kvp.Value.StateVal == value.State.StateVal);
var dateUTC = DateTime.SpecifyKind(date, DateTimeKind.Utc);
user.Holidays[dateUTC] = value.State;
}
// save
var updatedUser = await repository.UpdateEmployee(user);
return await Task.FromResult(new ValidationResponse<HolidayModel>()
{
IsValid = true,
Result = updatedUser.Holidays.ContainsKey(dateUTC) ? new HolidayModel() { Date = dateUTC, State = updatedUser.Holidays[dateUTC] } : null
});
}
}
The problem with my code is that it works only if I have weekly reccurrence. I need to make it work in order to have 2, 3, ... n weeks reccurrence.
How can I make it skip some weeks?
public class ReccurrenceModel
{
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public int WeeklyReccurrence { get; set; }
public State State { get; set; }
public bool IsMonday { get; set; }
public bool IsTuesday { get; set; }
public bool IsWednesday { get; set; }
public bool IsThursday { get; set; }
public bool IsFriday { get; set; }
public DateTime FromDateToReturn { get; set; }
public DateTime ToDateToReturn { get; set; }
}
The code is a bit convoluted, there are a lot of lines that do nothing at all.
Here I provide a sample of code that, albeit not elegant at all, provides you with the behaviour you need, the following code will create a list of days that are recurrent every 2, 3, whatever you need weeks you define in its call.
This method also accepts a list of DayOfWeek for which you want the recurrence to be created
private static void GetRecurrentDays(DateTime fromDate, DateTime untilDate, uint weeklyRecurrence, List<DayOfWeek> recurrenceDays)
{
var recurrenceDates = new List<DateTime>();
for (var dt = fromDate; dt < untilDate; dt = dt.AddDays(1))
{
if (recurrenceDays.Any(day => day.Equals(dt.DayOfWeek)))
{
var lastDate =
recurrenceDates
.LastOrDefault(date => date.DayOfWeek.Equals(dt.DayOfWeek));
// We multiply 7 days (a week) with weeklyRecurrence to
// calculate the appropiate date in which to add another day,
// calling with either 0 or 1 will calculate a weekly
// schedule
if (lastDate.Equals(DateTime.MinValue)
|| weeklyRecurrence.Equals(0)
|| ((dt - lastDate).Days % (7 * weeklyRecurrence)).Equals(0) )
{
recurrenceDates.Add(dt);
}
}
}
}
you can embed this code in yours in order to obtain the days with weekly recurrence and then, consume them further in your code
I have code to get all of values from table column and calculate average salary and max difference between salary dates.
Here is model for Salary
public int Id { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public Nullable<int> Employee_Id { get; set; }
public virtual Employee Employee { get; set; }
And here is Viewmodel code
public class SalariesViewModel
{
public string DepartmentName { get; set; }
public decimal AverageSalary { get; set; }
public IEnumerable<DateTime> Dates{ get; set; }
public double MaxDifference { get; set; }
}
Here is code where I calculate average salary
var maxdiff = 0;
List<SalariesViewModel> result = new List<SalariesViewModel>();
var employees = db.Employees.Select(x => new
{
Department = x.Department.Name,
Name = x.Name,
Salary = x.Salaries.OrderByDescending(y => y.Date).FirstOrDefault().Amount,
Date = x.Salaries.OrderByDescending(y => y.Date).FirstOrDefault().Date
});
var data = employees.GroupBy(m => m.Department).Select(x => new SalariesViewModel
{
DepartmentName = x.Key,
AverageSalary = x.Average(y => y.Salary),
Dates = x.Select(y=> y.Date).ToList()
}).ToList();
for (int i = 0; i < data.Count - 1; i++) {
}
return data;
}
Also I need to calculate max difference
I can select only dates and calculate differences between them and find max. And then push to ViewModel.
But I think, it not good experience.
How I can do this in data query or in ViewModel?
Update
This might give some clues to anyway heading down this rabbit hole
In response to Michael Randalls Answer
Yes. But you calculate difference beetween max and min values of date.
I need to calculate other. For example employee has 3 salary pays. I
need to show max time between those pays. So it would be 3 payment -2
payment , and 2 payment - 1 payment. and if for example 1 variant
difference is bigger, I need to show it.
If you need to find only the max difference between adjacent elements, without making the entire list of differences, you can Zip the list with itself after skipping the initial element, and get Max this way:
var maxDiff = dates.Zip(dates.Skip(1), (c, n) => (n-c).TotalSeconds).Max();
var maxdiff = 0;
for (int i = 0; i < dates.Count - 1; i++)
{
var result = (dates[i + 1] - dates[i]).TotalSeconds;
if (maxdiff < result)
{
maxdiff = result;
}
}
I am a gambling man, am i close?
var data = employees.GroupBy(m => m.Department).Select(x => new SalariesViewModel
{
DepartmentName = x.Key,
AverageSalary = x.Average(y => y.Salary),
MaxDifference = (x.Max(y => y.Date) - x.Min(y => y.Date)).TotalDays,
}).ToList()
I have a list of object with StartTime and EndTime.
I want to select all items that starts in given date and all that "connects" with minutes given from end time. There should not be any overlapping times, because I have checked those earlier.
F.e. Class
public class tObject
{
public int ID { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
So if i have tObject with these kind of start & end times :
ID = 1 StartTime = 21.1.2016 21:00 - EndTime = 22.1.2016 01:00
ID = 2 StartTime = 22.1.2016 01:30 - EndTime = 22.1.2016 05:00
ID = 3 StartTime = 22.1.2016 06:00 - EndTime = 22.1.2016 07:00
ID = 4 StartTime = 22.1.2016 07:50 - EndTime = 22.1.2016 08:00
ID = 5 StartTime = 22.1.2016 09:10 - EndTime = 22.1.2016 11:00
How can I select from ListItems that also 2,3,4 ID:s (those connects with 60 mins offset) will be selected to listTodaysItems ?
int iMinutes = 60;
List<tObject> listTodaysItems = listItems.Where(r => r.StartTime.Date == dtGivenDate.Date).ToList();
I know this is possible to do with for -loop but I'll have to do this often so I'll prefer single line select if it's possible. Also want to know if its possible to check other values in same list somehow.
So what is my best/fastest option here ?
If I understood you right, you need the following:
int threshold = 20;
var withinThreshold =
list.Where(x => list.Where(y => y.StartTime < x.StartTime && y.EndTime.AddMinutes(threshold) > x.StartTime)
.Any())
.Select(x => x).ToList();
This looks at every entry that started before the current one and checks if the current one is inside the threshold-range to any of those entries.
Here some full example program with test data, ready-to-go:
using System;
using System.Linq;
using System.Collections.Generic;
public class TObject
{
public int ID { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class Program
{
public static void Main()
{
List<TObject> list = new List<TObject>();
for(int i = 0; i < 100; i++)
{
list.Add(new TObject());
list[i].StartTime = (i == 0 ? DateTime.Now : list[i-1].EndTime.AddMinutes(i + 1));
list[i].EndTime = list[i].StartTime.AddMinutes(i);
Console.WriteLine(list[i].StartTime + " - " + list[i].EndTime);
}
int threshold = 20;
var withinThreshold =
list.TakeWhile( // Stop as soon as the an entry does not match the condition
x =>
!list.Where(y => y.StartTime < x.StartTime).Any() || // First entry
list.Where(y => y.StartTime < x.StartTime && y.EndTime.AddMinutes(threshold) > x.StartTime).Any()) // Following entries withing threshold
.Select(x => x)
.ToList();
Console.WriteLine("Within threshold: ");
withinThreshold.ForEach(x => Console.WriteLine(x.StartTime + " - " + x.EndTime));
}
}
I am trying to merge 2 classes into a third on the date properties, for binding to a graph I have already setup.
public class Last30DaysHours
{
public DateTime Date { get; set; }
public float Hours { get; set; }
public float LostHours { get; set; }
}
public class MachineHours
{
public DateTime Date { get; set; }
public float Hours { get; set; }
}
into
public class GraphLast30Days
{
public DateTime Date { get; set; }
public float Hours { get; set; }
public float LostHours { get; set; }
public float SelectedMachine { get; set; }
}
So far I have this linq statement which almost compiles.
The error with the current statment is "'x' does not exist in the current context".
I know what this means but I don't know how to make it accessable in the statement.
IEnumerable<GraphLast30Days> last30DaysMachineHoursSelect = _last30DaysMachineHours
.Select(p => (_last30Days
.Where(x => x.Date == p.Date) <=
(new GraphLast30Days {
Date = x.Date,
Hours = x.Hours,
LostHours = x.LostHours,
SelectedMachine = p.Hours
})));
My question is how do I make x accessable by the second half of the statement or what is a better statement to achieve the same results?
Thanks for the help.
You'd wont to use join to join your collections by date (not sure I got the correct proeprties, but you got the idea):
IEnumerable<GraphLast30Days> last30DaysMachineHoursSelect =
from machineHours in _last30DaysMachineHours
join last30 in _last30Days on machineHours.Date equals last30.Date
select
new GraphLast30Days {
Date = machineHours.Date,
Hours = machineHours.Hours,
LostHours = last30.LostHours,
SelectedMachine = machineHours.Hours
};
Or with alternative syntax:
var result = _last30DaysMachineHours.Join(_last30Days, graph => graph.Date, last30 => last30.Date,
(graph, last30) => new GraphLast30Days
{
Date = graph.Date,
Hours = graph.Hours,
LostHours = last30.LostHours,
SelectedMachine = graph.Hours
});
In case you don't wont to filter out missing values you'll need to do left join:
IEnumerable<GraphLast30Days> last30DaysMachineHoursSelect =
from last30 in _last30Days
from machineHours in _last30DaysMachineHours.Where (h => h.Date == last30.Date).DefaultIfEmpty()
select
new GraphLast30Days {
Date = last30.Date,
Hours = last30.Hours,
LostHours = last30.LostHours,
SelectedMachine = machineHours == null ? 0 : machineHours.Hours
}
I have a function that uses LINQ to get data from the database and then I call that function in another function to sum all the individual properties using .Sum() on each individual property. I was wondering if there is an efficient way to sum all the properties at once rather than calling .Sum() on each individual property. I think the way I am doing as of right now, is very slow (although untested).
public OminitureStats GetAvgOmnitureData(int? fnsId, int dateRange)
{
IQueryable<OminitureStats> query = GetOmnitureDataAsQueryable(fnsId, dateRange);
int pageViews = query.Sum(q => q.PageViews);
int monthlyUniqueVisitors = query.Sum(q => q.MonthlyUniqueVisitors);
int visits = query.Sum(q => q.Visits);
double pagesPerVisit = (double)query.Sum(q => q.PagesPerVisit);
double bounceRate = (double)query.Sum(q => q.BounceRate);
return new OminitureStats(pageViews, monthlyUniqueVisitors, visits, bounceRate, pagesPerVisit);
}
private IQueryable<OminitureStats> GetOmnitureDataAsQueryable(int? fnsId, int dateRange)
{
var yesterday = DateTime.Today.AddDays(-1);
var nDays = yesterday.AddDays(-dateRange);
if (fnsId.HasValue)
{
IQueryable<OminitureStats> query = from o in lhDB.omniture_stats
where o.fns_id == fnsId
&& o.date <= yesterday
&& o.date > nDays
select new OminitureStats (
o.page_views.GetValueOrDefault(),
o.monthly_unique.GetValueOrDefault(),
o.visits.GetValueOrDefault(),
(double)o.bounce_rate.GetValueOrDefault()
);
return query;
}
return null;
}
public class OminitureStats
{
public OminitureStats(int PageViews, int MonthlyUniqueVisitors, int Visits, double BounceRate)
{
this.PageViews = PageViews;
this.MonthlyUniqueVisitors = MonthlyUniqueVisitors;
this.Visits = Visits;
this.BounceRate = BounceRate;
this.PagesPerVisit = Math.Round((double)(PageViews / Visits), 1);
}
public OminitureStats(int PageViews, int MonthlyUniqueVisitors, int Visits, double BounceRate, double PagesPerVisit)
{
this.PageViews = PageViews;
this.MonthlyUniqueVisitors = MonthlyUniqueVisitors;
this.Visits = Visits;
this.BounceRate = BounceRate;
this.PagesPerVisit = PagesPerVisit;
}
public int PageViews { get; set; }
public int MonthlyUniqueVisitors { get; set; }
public int Visits { get; set; }
public double PagesPerVisit { get; set; }
public double BounceRate { get; set; }
}
IIRC you can do all the sums in one go (as long as the query is translated to SQL) with
var sums = query.GroupBy(q => 1)
.Select(g => new
{
PageViews = g.Sum(q => q.PageViews),
Visits = g.Sum(q => q.Visits),
// etc etc
})
.Single();
This will give you one object which contains all the sums as separate properties.
I found out why it was throwing the NotSupportedException. I learned that Linq to Entity does not support constructors with parameters, So deleted the constructors and made changes in my query. I am a novice C# programmer, so let me know if my solution could be improved, but as of right now it is working fine.
public class OminitureStats
{
public int PageViews { get; set; }
public int MonthlyUniqueVisitors { get; set; }
public int Visits { get; set; }
public double PagesPerVisit { get; set; }
public double BounceRate { get; set; }
}
private IQueryable<OminitureStats> GetOmnitureDataAsQueryable(int? fnsId, int dateRange)
{
var yesterday = DateTime.Today.AddDays(-1);
var nDays = yesterday.AddDays(-dateRange);
if (fnsId.HasValue)
{
IQueryable<OminitureStats> query = from o in lhDB.omniture_stats
where o.fns_id == fnsId
&& o.date <= yesterday
&& o.date > nDays
select new OminitureStats() {
o.page_views.GetValueOrDefault(),
o.monthly_unique.GetValueOrDefault(),
o.visits.GetValueOrDefault(),
(double)o.bounce_rate.GetValueOrDefault()
};
return query;
}
return null;
}