C# Linq how to add each missing year and month to data - c#

I have a list of the following class:
public class Data
{
public string Sector { get; set; }
public string Company { get; set; }
public string Branch { get; set; }
public string Category { get; set; }
public string Item { get; set; }
public string Currency { get; set; }
public int Year { get; set; }
public int Quarter { get; set; }
public int Month { get; set; }
public double Amount { get; set; }
}
With the date range given by the user, I am bringing data on year and month basis. However there is no data for some year and month in the given date range.
var dateRange = new List<DateTime>();
for (DateTime date = Report.Parameter.BeginDate; date <= Report.Parameter.EndDate; date.AddMonths(1))
{
dateRange.Add(new DateTime(date.Year, date.Month, 1));
}
For each year and month in the given date range, I want the Data object to come with a Amount column of 0 (zero).
This data should be added once for each Sector, Company, Branch and Category columns.
Example:
How do I do this with linq?

If you really need to use Linq, it would look like this:
public void MyFunction(List<Data> userData)
{
var beginDate = DateTime.Today;
var endDate = DateTime.Today.AddYears(2);
var monthsApart = 12 * (beginDate.Year - endDate.Year) + beginDate.Month - endDate.Month;
var dateRange = Enumerable.Range(0, monthsApart).Select(offset => beginDate.AddMonths(offset));
var difference = dateRange.Where(item => !(userData.Any(uData => uData.Year == item.Year && uData.Month == item.Month)));
userData.AddRange(difference.Select(date => new Data() {Year = date.Year, Month = date.Month, Amount = 0}));
}
This is just an example. You need to add your logic, where it is needed.
You mentioned This data should be added once for each Sector, Company, Branch and Category columns
If you want to do this, you have to call
userData.AddRange(difference.Select(date => new Data() {Year = date.Year, Month = date.Month, Amount = 0}));
as many times as you have combinations of Sector, Company, Branch and Category.
I hope this helps to understand and my explanation is not too complex.

Related

c# linq one to many filter by child property

I have a product model class with many versions childs:
public class Product {
public int Id {
get;
set;
}
public string Name {
get;
set;
} = "";
public List <Version> Versions {
get;
set;
} = new List <Version> ();
}
public class Version {
public int Id {
get;
set;
}
public string Name {
get;
set;
} = "";
public DateTime ReleaseDate {
get;
set;
} = new DateTime();
}
I want to filter by a child property: releasedate:
var releaseDateFrom = DateTime.ParseExact("20110720", "yyyyMMdd", CultureInfo.InvariantCulture);
var products = ProductRepository.GetAll();
List <ProductDto> list;
list = products.Where(
p => (p.Name!.StartsWith("p") &&
p.Name!.Length > 0) &&
GetReleaseDateFromVersion(p.Versions.OrderBy(p => p.ReleaseDate).LastOrDefault()) > releaseDateFrom
).Select(p =>
new ProductDto {
Id = p.Id,
Name = p.Name,
LatestVersionDate = GetReleaseDateFromVersion(p.Versions.OrderBy(p => p.ReleaseDate).LastOrDefault())
}).ToList();
DateTime GetReleaseDateFromVersion(Version v) => v == null ? new DateTime() : v.ReleaseDate;
list.ForEach(p => Console.WriteLine("{0} {1}", p.Name, p.LatestVersionDate));
The result output shows that the filter date > 20 July 2022 does not work:
product 1 11/20/2013 00:00:00
product 2 06/20/2013 00:00:00
Product 2 should not be listed.
What am I doing wrong? Any suggestions how to fix this query?
The code can be run here: https://dotnetfiddle.net/esRdOg
Edit after comment from #SergeySosunov
As #SergeySosunov correctly noted: My filter date was wrong. The above code works as expected.

How to convert List<CustomType> to Dictionary<int, List<CustomType>>

Lets say that we have this custom type:
public class Holiday
{
public Guid Id { get; } = Guid.NewGuid();
public string holidayName { get; set; };
public DateTime fromDate { get; set; };
public DateTime toDate { get; set; };
public int year { get; set; };
}
I need to convert list of Holidays (List<Holiday>) to an dictionary (Dictionary<int, List<Holiday>>). The keys are distinct years and the values are Lists of Holidays those belong to year.
I have tried to do that by looking at this answer/Question but without success.
You can do this using the GroupBy method in LINQ, which
Groups the elements of a sequence according to a specified key
selector function
In your case, the key would be year so the GroupBy syntax would look as follows:
List<Holiday> holidays = new List<Holiday>
{
new Holiday
{
year = 1999,
holidayName = "Easter"
},
new Holiday
{
year = 1999,
holidayName = "Christmas"
},
new Holiday
{
year = 2000,
holidayName = "Christmas"
}
};
Dictionary<int, List<Holiday>> holidaysByYear = holidays
.GroupBy(h => h.year)
.ToDictionary(h => h.Key, h => h.ToList());
foreach (KeyValuePair<int, List<Holiday>> holidaysInYear in holidaysByYear)
{
Console.WriteLine($"Holidays in {holidaysInYear.Key}");
foreach (Holiday holiday in holidaysInYear.Value)
{
Console.WriteLine(holiday.holidayName);
}
}
Which produces output as:

How to count rows of a table grouped by shortdatestring?

We have an database with an this structure:
public partial class ChartData
{
public int Id { get; set; }
public DateTime Timestamp { get; set; }
public string Function { get; set; }
public int Duration { get; set; }
public bool IsError { get; set; }
}
Now we want to group the entries of this database by Timestamp.ToShortDateString() and then count the entries belonging to this date.
So for example we have:
2019-06-04 11:54:02,135,someFunction,30,False,
2019-06-04 11:55:03,135,someFunction,230,False,
2019-06-04 11:56:03,150,someFunction,4,True,
2019-06-05 11:54:03,230,someFunction,46,False,
2019-06-05 11:55:03,230,someFunction,46,False,
And I want this result:
{date: 2019-06-04, rows: 3}
{date: 2019-06-05, rows: 2}
public List <LogFileDTO> GetLogFilesData()
{
var items = db.ChartDatas.GroupBy(x = > new {
x.Timestamp.ToShortDateString
}).Select(x = > new LogFileDTO {
date = x.Timestamp.First(),
rows = x.Count ?
}).ToList();
}
So I do not really know how to group this input by date and then count the rows of each group.
You simply need to group on the Date property of the TimeStamp and then project using Select on the Key of each IGropuing and Count like below :
var items = db.ChartDatas.GroupBy(x => x.Timestamp.Date) // group only on Date
.Select(x => new LogFileDTO
{
Date = x.Key.ToShortDateString(),
Rows = x.Count()
}).ToList();
Key will contain only date part of DateTime object and Count() will tell the number of rows for that date in the group.
Hope it helps!

How to caculate the working hours for employees when i have the time when the got "in" and "out"

So i have a database that is connected with a device at the door that employees check when they come in and go out now i can get the history of statuses but i wanted to know how can I calculate the working hours for a day a week and a month for each employee. I got a this class for employee .
public class Employee : BaseEntity
{
public Employee()
{
this.HistoryOfStatuses = new List<Checkinout>();
this.TodayCheckedStatus = new List<Checkinout>();
}
public string Name { get; set; }
public string Department { get; set; }
public string CardNumber { get; set; }
public string Status { get; set; }
public byte[] Picture { get; set; }
public Checkinout ActualCheckinStatuse { get; set; }
public List<Checkinout> HistoryOfStatuses { get; set; }
public List<Checkinout> TodayCheckedStatus { get; set; }
public int UserId { get; internal set; }
And this is the checking status class
public class Checkinout : BaseEntity
{
public Checkinout()
{
}
public int EmployeeId { get; set; }
public string CheckStatus { get; set; }
public DateTime CheckTime { get; set; }
public Employee EmployeeObject { get; set; }
}
My controller looks something like this
public IActionResult Index()
{
using (var context = new RSAT.Api.Data.Proxy.ATT2018NOVUSContext())
{
var baseViewModel = base.GetLayoutViewModel();
var viewModel = new HomeViewModel()
{
User = baseViewModel.User,
RoleCollection = baseViewModel.RoleCollection,
TableCollection = baseViewModel.TableCollection,
//Olap = baseViewModel.Olap,
//Localization = baseViewModel.Localization,
EmployeeCollection = (from userinfo in context.Userinfo
join department in context.Dept on userinfo.Deptid equals department.Deptid
select new Employee()
{
Id = userinfo.Userid,
Name = userinfo.Name,
Picture = userinfo.Picture,
Department = department.DeptName,
CardNumber = userinfo.CardNum,
Status = userinfo.UserFlag.ToString(),
ActualCheckinStatuse = (from checkinout in context.Checkinout
join status in context.Status on checkinout.CheckType equals status.Statusid
where checkinout.Userid == userinfo.Userid
orderby checkinout.CheckTime descending
select new Checkinout
{
CheckStatus = status.StatusText,
CheckTime = checkinout.CheckTime
}).FirstOrDefault()
}).ToList()
};
return View(viewModel);
}
}
public IActionResult WorkingHours()
{
var inTime = "10:00";
var outTime = DateTime.Now.TimeOfDay;
var totalHours = Convert.ToDateTime(inTime).TimeOfDay.Subtract(outTime);
}
I wanted someones help to clear me out how to do this and how can i connect the last code in my controller .
Does your model have a property somewhere determining whether an entry is an in or out check? You will need this.
What you can do now is create a method that takes two dates that form a range. You can return the total work hours inside this range. You can then easily create a method for a week, month, year, where you specify the date ranges to this method.
Here's some pseudocode to get you started.
decimal GetRangeWorkHours(Employee employee, DateTime startOfRange, DateTime endOfRange){
//1. Ask the database for all the entries from this employee within the range of dates.
//2. Sort them by date, figuring out what entry the start/end is. You might get some edgecases where employees clock out midday and then clock back in later on the day. You can work this out using TimeSpans.
//3. Calculate the difference between all the now paired in/out entries.
//4. Sum the results of the previous step, return the result.
}
Consuming for week/month/year becomes easy
Employee someEmployee = new Employee(/*wetherever you use to identify employees*/);
//Replace these 2 values with whatever you need to provide your user with.
DateTime startOfRange = new DateTime(2019, 1, 1);
DateTime endOfRange = startOfRange.AddDays(7);
decimal workHours = GetRangeWorkHours(someEmployee, startOfRange, endOfRange);

Linq Grouping and averages

I have two objects one is a car object and the other object I use to log how many car objects are on shift and record what properties these cars have.
public class Car
{
public int Id { get; set; }
public bool OnShift { get; set; }
public bool HasExtraBaggageSpace { get; set; }
}
public class Log
{
public DateTime TimeStamp { get; set; }
public int CarId { get; set; }
public bool HasExtraBaggageSpace { get; set; }
}
Every five minutes the app selects all the cars on shift and writes the information to a log object and inserts them into a List Logs.
After three weeks of logging I would now like to return a number which reflects the average of the last three weeks . Example:
How many cars with HasExtraBaggageSpace can I expect on a thursday at 14:00.
public class myApp
{
public class AverageReturnArgs
{
public int Hour { get; set; }
public int Minute { get; set; }
public int Count { get; set; }
}
public AverageReturnArgs GetAverage(List<Log> logs, DateTime TimeReq)
{
int hour = TimeReq.Hour;
int min = TimeReq.Minute;
var average = logs.GroupBy(grpByHourMin => new
{
hour = grpByHourMin.TimeStamp.Hour,
min = grpByHourMin.TimeStamp.Minute
}).Select(av => new AverageReturnArgs()
{
Hour = av.Key.hour,
Minute = av.Key.min,
Count = av.Average(x => x.HasExtraBaggageSpace)
});
}
}
This is producing a compiler error.
Count = av.Average(x => x.HasExtraBaggageSpace)
Any ideas how I could accomplish this?
How would you calculate the average of boolean values ?
I think the Count aggregate should be what you are looking for:
Count = av.Count(x => x.HasExtraBaggageSpace)
EDIT If you mean to calculate the percentage of cars having ExtraBaggageSpace you may try something like this :
Count = av.Average(x => x.HasExtraBaggageSpace ? 1 : 0)
With use of the ternary operator this expression convert your boolean value to an integer and calculate the average (that will be a Double).
EDIT 2
Here is what your line should look like.
Count should be made of type Double.
Count = av.Average(x => av.Count(y=>y.HasExtraBaggageSpace))
EDIT 3
Ok the logic was all wrong :
public AverageReturnArgs GetAverage(List<Log> logs, DateTime TimeReq)
{
int hour = TimeReq.Hour;
int min = TimeReq.Minute;
var average = logs
.Where(log => log.TimeStamp.Hour == hour && log.TimeStamp.Minute == min)
.GroupBy(grp => grp.TimeStamp)
.Select(av => new AverageReturnArgs()
{
Hour = hour,
Minute = min,
Count = av.Average(x => av.Count(y=>y.HasExtraBaggageSpace))
});
}

Categories