I am trying to retrieve each class attendance as follows, but I wonder there is a better way to handle it, instead of calling three times GetAttendanceLastMonth method for each class, is there a way to call only once and get all these information.
List<Attendance> chemistry = GetAttendanceLastMonth(schoolId, chemistryId);
List<Attendance> math = GetAttendanceLastMonth(schoolId, mathId);
List<Attendance> music = GetAttendanceLastMonth(schoolId, musicId);
//Plotting them on the chart based on date (x axis) and attendance number(yaxis) values
public List<Attendance> GetAttendanceLastMonth(string schoolId, string classId)
{
try
{
var attendanceNumber = Repository.Get(x => x.SchoolId.Equals(schoolId)).Where(x => x.ClassId.Equals(classId) &&
(DateTime.Now.Date.Subtract(x.AttendanceDate.Date)).TotalDays >= 0 &&
(int)(DateTime.Now.Date - x.AttendanceDate.Date).TotalDays <= 30).ToList();
attendanceNumber.Sort((x, y) => x.AttendanceDate.CompareTo(y.AttendanceDate));
return attendanceNumber ;
}
catch (Exception ex)
{
}
}
You could return a dictionary, the key is the ClassId and the value is a List<Attendance> and you can use Contains to check multiple ClassIds:
public Dictionary<string, List<Attendance>> GetAttendanceLastMonth(string schoolId, params string[] classIds)
{
DateTime monthStart = DateTime.Today.AddDays(-DateTime.Today.Day);
DateTime nextMonthStart = monthStart.AddMonths(1);
Dictionary<string, List<Attendance>> attendancesDictionary = Repository
.Get(x => x.SchoolId == schoolId)
.Where(x => classIds.Contains(x.ClassId))
.Where(x => x.AttendanceDate.Date >= monthStart && x.AttendanceDate.Date < nextMonthStart)
.GroupBy(x => x.ClassId)
.ToDictionary(g => g.Key, g => g.OrderBy(x => x.AttendanceDate).ToList());
return attendancesDictionary;
}
You can access each list via key:
Dictionary<string, List<Attendance>> attendancesDictionary =
GetAttendanceLastMonth(schoolId, chemistryId, mathId, musicId);
// for example the music-list:
List<Attendance> music = attendancesDictionary[musicId];
Related
Im having 5 users and 5 services.
I must catch if more than one user(s) have same service. So I created a dictionary:
//JMBGS represents users ids
List<string> multipleJMBGs = new List<string>();
multipleJMBGs.Add(data.jMBG);
multipleJMBGs.Add(data.secondPersonJMBG);
multipleJMBGs.Add(data.thirdPersonJMBG);
multipleJMBGs.Add(data.fourthPersonJMBG);
multipleJMBGs.Add(data.fifthPersonJMBG);
multipleJMBGs.RemoveAll(x => string.IsNullOrEmpty(x));
//object Service has Id property, and I need to check if more than one user has the same service
var serviceForClient = data?.Schedules[0]?.Service;
var serviceForFirstFamilyMember = data?.Schedules[1]?.Service;
var serviceForSecondFamilyMember = data?.Schedules[2]?.Service;
var serviceForThirdFamilyMember = data?.Schedules[3]?.Service;
var serviceForFourthFamilyMember = data?.Schedules[4]?.Service;
Dictionary<string, EmployeeTableDay.ServiceDTO> userService = new Dictionary<string, EmployeeTableDay.ServiceDTO>();
userService.Add(data.jMBG, serviceForClient);
userService.Add(data.secondPersonJMBG, serviceForFirstFamilyMember);
userService.Add(data.thirdPersonJMBG, serviceForSecondFamilyMember);
userService.Add(data.fourthPersonJMBG, serviceForThirdFamilyMember);
userService.Add(data.fifthPersonJMBG, serviceForFourthFamilyMember);
What I want to have as flag (true/false):
different users can have different services
different users can have same services
same users, can not have same service
same users can have multiple services
UPDATE #1:
List<string> multipleJMBGs = new List<string>();
multipleJMBGs.Add(data.jMBG);
multipleJMBGs.Add(data.secondPersonJMBG);
multipleJMBGs.Add(data.thirdPersonJMBG);
multipleJMBGs.Add(data.fourthPersonJMBG);
multipleJMBGs.Add(data.fifthPersonJMBG);
multipleJMBGs.RemoveAll(x => string.IsNullOrEmpty(x));
var countedSchedules = data?.Schedules?.Count();
var serviceIdForClient = 0;
var serviceIdForFirstFamilyMember = 0;
var serviceIdForSecondFamilyMember = 0;
var serviceIdForThirdFamilyMember = 0;
var serviceIdForFourthFamilyMember = 0;
List<int> servicesIds = new List<int>();
if(countedSchedules >= 1 && data?.Schedules[0]?.Service != null)
{
serviceIdForClient = data.Schedules[0].Service.Id;
servicesIds.Add(serviceIdForClient);
}
if (countedSchedules >= 2 && data?.Schedules[1]?.Service != null)
{
serviceIdForFirstFamilyMember = data.Schedules[1].Service.Id;
servicesIds.Add(serviceIdForFirstFamilyMember);
}
if (countedSchedules >= 3 && data?.Schedules[2]?.Service != null)
{
serviceIdForSecondFamilyMember = data.Schedules[2].Service.Id;
servicesIds.Add(serviceIdForSecondFamilyMember);
}
if (countedSchedules >= 4 && data?.Schedules[3]?.Service != null)
{
serviceIdForThirdFamilyMember = data.Schedules[3].Service.Id;
servicesIds.Add(serviceIdForThirdFamilyMember);
}
if (countedSchedules == 5 && data?.Schedules[4]?.Service != null)
{
serviceIdForFourthFamilyMember = data.Schedules[4].Service.Id;
servicesIds.Add(serviceIdForFourthFamilyMember);
}
servicesIds = servicesIds.Where(x => x > 0).ToList();
//because dictionary throws an exception
for (int i = 0; i < countedSchedules; i++)
{
multipleJMBGs[i] = i + " _ " + multipleJMBGs[i];
}
//create dictionary from two lists
var dic = multipleJMBGs.Zip(servicesIds, (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v);
var duplicateServices = dic.GroupBy(x => x.Value).Where(x => x.Count() > 1)
.Select(x => new { ServiceId = x.Key, Users = x.ToList() });
var duplicateUsers = dic.GroupBy(x => x.Key.Substring(3,x.Key.Length-3)).Where(x => x.Count() > 1)
.Select(x => new { User = x.Key, Services = x.ToList() });
if(duplicateServices > 1 && duplicateUsers.Count > 1)
{
//show a message that it ca't be proceeed further
}
else
{
//continue
}
You can group the services by Id to get information on duplicates:
var groups = data?.Schedules
.Select((service, index) => (service, index))
.GroupBy(x => x.service.Id);
foreach (var g in groups) {
if (g.Count() == 1) {
Console.WriteLine(
$"Service Id {g.Key} exists once for user index {g.First().index}");
} else {
string indexes = String.Join(", ", g.Select(t => t.index.ToString()));
Console.WriteLine(
$"Service Id {g.Key} is shared by user indexes {indexes}");
}
}
If you don't call multipleJMBGs.RemoveAll(...), then the indexes in the users list will match the indexes of the services. So you can easily get the JMBG assigned to a service.
var groups = data?.Schedules
.Select((service, index) => (service, index))
.GroupBy(x => x.service.Id);
foreach (var g in groups) {
if (g.Count() == 1) { // Service is unique
var service = g.First().service;
int index = g.First().index;
string user = multipleJMBGs[index];
...
} else { // Services assigned to more than one user.
var serviceId = g.Key;
var service = g.First().service;
foreach (var t in g) {
int index = t.index;
string user = multipleJMBGs[index];
...
}
...
}
}
Purely answering the question on how to check for duplicates within your dictionary:
You could group the values of your dictionary and look for duplicates that way.
var containsDuplicates = userService.GroupBy(x => x.Value).Any(x => x.Count() > 1);
If you want to actually see which services are used multiple times by which users, create the following grouping:
var duplicateServices = userService.GroupBy(x => x.Value).Where(x => x.Count() > 1)
.Select(x => new { Service = x.Key, Users = x.ToList() });
UPDATE #1:
You don't necessarily have to group up both of these lists if you just need to check if any of the two contain duplicates.
We could simply compare the total count of the lists with the count of the list when we remove all possible duplicates:
var duplicateServices = countedSchedules != data?.Schedules?.Distinct().Count();
var duplicateUsers = multipleJMBGs.Count() != multipleJMBGs.Distinct().Count();
UPDATE #2:
Sounds like you want to join the two lists together and not necessarily convert it to a dictionary, what we want to do instead is create a new List so we can group that on the combination of the two properties.
var containsDuplicates = multipleJMBGs.Zip(serviceIds, (JMBG, serviceId) => new { JMBG, serviceId })
.Where(x => !string.IsNullOrEmpty(x.JMBG) && x.serviceId > 0) // Filter AFTER zipping the two lists together
.GroupBy(x => x).Any(x => x.Count() > 1);
Do be aware that you are removing elements in both lists, meaning that the order might not be correct. You could instead add a Where clause after you zip the lists like in the example above.
What I have is a string data type which stores the duration.
I am a looking for sum of the duration and then average of that sum.
I am using ASP.NET MVC.
Example:
00:30:21
00:40:01
00:21:10
Model class
public DateTime? FeedbackDateTime { get; set; }
public DateTime? FeedbackSharedDateTime { get; set; }
public string AuditorAHT { get; set; }
ReportVM To Group Data and display in the View
public string FeedbackSharedBy { get; set; }
public int AuditCount { get; set; }
public string AudtAht { get; set; }
Controller that saves the action perform by auditor as duration in
public string AuditorAHT { get; set; }
dto.FeedbackSharedDateTime = DateTime.Now;
string ahtString = string.Format("{0:hh\\:mm\\:ss}", dto.FeedbackSharedDateTime - dto.FeedbackDateTime);
dto.AuditorAHT = ahtString;
db.SaveChanges();
Below Action should display Auditors Name, Count, and Average Time spent. From which Name and Count is working but not the Average Time Spend
var audtName = db.Chats.Where(x => System.Data.Entity.DbFunctions.TruncateTime(x.MSTChatCreatedDateTime) >= mostRecentMonday
&& System.Data.Entity.DbFunctions.TruncateTime(x.MSTChatCreatedDateTime) <= weekEnd && x.Feedback != null && x.FeedbackSharedBy != null).Select(x => new {
x.FeedbackSharedBy,
x.AuditorAHT
}).ToList() // this hits the database
// We need to do grouping in the code (rather than the db)
// because timespans are stored as strings
.GroupBy(e => e.FeedbackSharedBy)
.Select(g => new ReportVM
{
FeedbackSharedBy = g.Key,
AuditCount = g.Count(),
AudtAht = TimeSpan.FromSeconds(g.Sum(t => TimeSpan.Parse(t.AuditorAHT).TotalSeconds / g.Count())).ToString()
})
.OrderByDescending(s => s.AuditCount).ToList();
ViewBag.AudtReport = audtName;
Above COde is working for me, managed to make it work.
You can convert the string duration into a TimeSpan type and use this to do time calculations. To convert it you can use TimeSpan.Parse() or if you have a fix format use TimeSpan.ParseExact().
With TimeSpan you can get the the totals out of it with the various .Total*() methods. You also can get the internal ticks count with .Ticks. That's the one with the highest precision.
Now its a simple math: sum of all ticks / count = average ticks
You can pass this average ticks count into a TimeSpan again to grab it as .TotalMilliseconds() or output it formatted with .ToString().
Here is a basic sample:
using System;
public class Program
{
public static void Main()
{
var duration1 = TimeSpan.Parse("00:30:21");
var duration2 = TimeSpan.Parse("00:40:01");
var duration3 = TimeSpan.Parse("00:21:10");
var totalDuration = duration1.Add(duration2).Add(duration3);
var averageDurationTicks = totalDuration.Ticks / 3;
var averageDuration = TimeSpan.FromTicks(averageDurationTicks);
Console.WriteLine($"Total duration: {totalDuration}, Average duration: {averageDuration}");
}
}
Here is a .Net Fiddle: https://dotnetfiddle.net/1Q9tmV
After spending lot of time and with help of tymtam made the code work with below code.
var audtName = db.Chats.Where(x => System.Data.Entity.DbFunctions.TruncateTime(x.MSTChatCreatedDateTime) >= mostRecentMonday
&& System.Data.Entity.DbFunctions.TruncateTime(x.MSTChatCreatedDateTime) <= weekEnd && x.Feedback != null && x.FeedbackSharedBy != null).Select(x => new {
x.FeedbackSharedBy,
x.AuditorAHT
}).ToList() // this hits the database
// We need to do grouping in the code (rather than the db)
// because timespans are stored as strings
.GroupBy(e => e.FeedbackSharedBy)
.Select(g => new ReportVM
{
FeedbackSharedBy = g.Key,
AuditCount = g.Count(),
AudtAht = TimeSpan.FromSeconds(g.Sum(t => TimeSpan.Parse(t.AuditorAHT).TotalSeconds / g.Count())).ToString()
})
.OrderByDescending(s => s.AuditCount).ToList();
ViewBag.AudtReport = audtName;
If a second precision is enough for you you could combine Linq's Sum and Average with TimeSpan's Parse and TotalSeconds:
Sum = TimeSpan.FromSeconds(g.Sum(t => TimeSpan.Parse(t.Time).TotalSeconds)),
Avg = TimeSpan.FromSeconds(g.Average(t => TimeSpan.Parse(t.Time).TotalSeconds))
Here is a full example:
var data = new []{
new { X = "A", Time = "00:30:21"},
new { X = "B", Time = "00:40:01"},
new { X = "B", Time = "00:21:10"}
};
var grouped = data
.GroupBy(e => e.X)
.Select( g => new {
X = g.Key,
Count = g.Count(),
Sum = TimeSpan.FromSeconds(g.Sum(t => TimeSpan.Parse(t.Time).TotalSeconds)),
Avg = TimeSpan.FromSeconds(g.Average(t => TimeSpan.Parse(t.Time).TotalSeconds))
});
foreach (var item in grouped)
{
Console.WriteLine( $"'{item.X}' has {item.Count} item(s), sum = {item.Sum}, avg = {item.Avg}");
}
This produces:
'A' has 1 item(s), sum = 00:30:21, avg = 00:30:21
'B' has 2 item(s), sum = 01:01:11, avg = 00:30:35.5000000
You could use TotalMilliseconds + FromMillisecond or even go super precise Ticks.
Variant with Aggregate
Another option is to Parse earlier:
Sum = g.Select(e => TimeSpan.Parse(e.Time)).Aggregate((t1, t2) => t1 + t2),
Avg = g.Select(e => TimeSpan.Parse(e.Time)).Aggregate((t1, t2) => t1 + t2) / g.Count()
For LINQ to Entities
As you report in your comment if we try the above with your real code it results in LINQ to Entities does not recognize the method 'System.TimeSpan FromSeconds(Double)' method, and this method cannot be translated into a store expression. (With the same for TimeSpan.Parse.
Because of this we will would need to do the grouping in the code. This is less efficient that if the TimeSpan was in the database as TimeSpan.
var grouped = db.Chats
.Where(...)
.Select( x => new {
x.FeedbackSharedBy,
x.AuditorAHT
})
.ToList() // this hits the database
// We need to do grouping in the code (rather than the db)
// because timespans are stored as strings
.GroupBy(e => e.FeedbackSharedBy)
.Select(g => new
{
FeedbackSharedBy = g.Key,
AuditCount = g.Count(),
AuditorAHTSumSeconds = TimeSpan.FromSeconds(g.Sum(t => TimeSpan.Parse(t.AuditorAHT).TotalSeconds) / g.Count())
.ToString(),
})
.OrderByDescending(s => s.AuditCount)
.ToList(); // Optional
I am trying to search through a group of days, and determine if a worker has worked that day, and get a total of days worked. The below works, but is terribly inefficient since even after it finds a guy worked a day it keeps looking through the rest of those days. If I could somehow increment the outer ForEach loop when the inner condition (day worked) is satisfied it would surely be faster.
totalDaysWorked is what I'm after below:
public class StationSupportRequest
{
public string RequestNum;
public string Status;
public string Prefix;
public string PlantLoc;
public DateTime Date;
public string Departmnt;
public DateTime Time;
public string StationID;
public string Fixture;
public string Supervisor;
public string PartNo;
public string SerialNum;
public string FailedStep;
public string Reason;
public string OtherReason;
public string Details;
public string Urgency;
public DateTime Date_1;
public DateTime Time_1;
public DateTime Date_2;
public DateTime Time_2;
public string ProblemFound;
public string SolutionCode;
public string Solution;
public double ServiceTechHrs;
public double ServiceEngHrs;
public string DocHistory;
public DateTime CloseDate;
public DateTime IniDate;
public DateTime IniTime;
public string MOT;
public string Initiator;
public string Notification;
public string ServiceTech;
public string ServiceEng;
public string SolutionCode_1;
public string Solution_1;
public string UpdatedBy;
public List<string> UpdatedByList;
public string Revisions;
public List<DateTime> RevisionsDateTime;
public List<WorkedDatapoint> WorkedDataPointsList;
}
public class WorkedDatapoint
{
public string AssignerName { get; set; }
public string AssigneeName { get; set; }
public DateTime Date { get; set; }
public bool AssignedToOther { get; set; }
}
var DateRange = SSRList.Where(y => y.IniDate >= IniDate && y.CloseDate < EndDate);
//DateRange = DateRange.Where(dr => dr.Fixture != null && dr.Fixture.Length == 6); //To get valid fixtures if pivoting on "Fixture"
var groupedData = DateRange.GroupBy(x => new { DS = x.ServiceTech }).Select(x =>
{
double totalSsrsWorkedOn = x.Select(y => y.RequestNum).Count();
IEnumerable<TimeSpan> hoursWorked = x.Select(y => y.CloseDate - y.IniDate.AddDays(GetWeekendDaysToSubtract(y.IniDate, y.CloseDate)));
var averageReactionTimeMinutes = x.Where(d => d.IniDate != null && d.Revisions != null)
.Average(d => ((DateTime.Parse(d.Revisions.Split(',')[0]) - (DateTime)d.IniDate)).Minutes);
double[] listOfMinutesOpenTime = x.Where(d => d.IniDate != null && d.Revisions != null)
.Select(d => Convert.ToDouble(((DateTime.Parse(d.Revisions.Split(',')[0]) - (DateTime)d.IniDate)).Minutes))
.ToArray();
double[] listOfDaysOpenTime = x.Where(d => d.IniDate != null && d.CloseDate != null)
.Select(d => ((DateTime)d.CloseDate - (DateTime)d.IniDate.AddDays(GetWeekendDaysToSubtract(d.IniDate, d.CloseDate))).TotalDays)
.ToArray();
string testtech = x.Select(y => y.ServiceTech).FirstOrDefault();
List<DateTime> totalDaysInDateRange = Enumerable.Range(0, 1 + EndDate.Subtract(IniDate).Days)
.Select(offset => IniDate.AddDays(offset)).ToList();
double totalHoursLogged = x.Sum(d => d.ServiceEngHrs) + x.Sum(d => d.ServiceTechHrs);
int assignedToOthersCount = x.SelectMany(y => y.WorkedDataPointsList)
.Where(z => z.AssignerName.Contains(testtech) && z.AssignedToOther == true)
.Count();
int brokenWiresFixed = x.Where(d => d.SolutionCode != null)
.Where(d => d.SolutionCode.Contains("A01 -") ||
d.SolutionCode.Contains("F01 -") ||
d.SolutionCode.Contains("S01 -")).Count();
int npfResults = x.Where(d => d.ProblemFound != null).Where(d => d.ProblemFound.Contains("NPF")).Count();
int totalDaysWorked = 0;
List<DateTime> workingDatesList = new List<DateTime>();
totalDaysInDateRange.ForEach((day) =>
{
x.Select(y => y.WorkedDataPointsList).ForEach((WorkedDataPoint) =>
{
IEnumerable<WorkedDatapoint> dateList = WorkedDataPoint
.Where(y => testtech == y.AssignerName)
.DistinctBy(z => z.Date.Date);
foreach ( WorkedDatapoint date in dateList)
{
if (x.Any(b => b.Date.Date.Date == date.Date.Date.Date))
{
workingDatesList.Add(date.Date.Date.Date);
break;
}
}
});
});
workingDatesList.Dump("WorkingDatesList");
totalDaysWorked = workingDatesList.DistinctBy(b => b.Date).Count();
/*int totalDaysWorked = 0;
totalDaysInDateRange.ForEach((day) =>
{
if (AssignersList.Where(d => testtech.Contains(d.AssignerName))
.DistinctBy(d => d.Date.Date)
.Any(d => d.Date.Date == day.Date))
{
totalDaysWorked++;
}
}); TODO: Delete this once new is working*/
return new
{
//SSRs = x,
//Station = x.Select(d => d.StationID).FirstOrDefault(),
//Fixture = x.Select(d => d.Fixture).FirstOrDefault(),
//ProductTested = x.Select(d => d.Details).FirstOrDefault(),
TestTech = testtech,
//TestEng = x.Select(d => d.ServiceEng).Distinct().Where(d => d.Length > 0),
TotalSSRsWorkedOn = Math.Round(totalSsrsWorkedOn, 4),
TotalHoursLogged = Math.Round(totalHoursLogged, 4),
AssignedToOthersCount = assignedToOthersCount,
AssignedToOthersPercentage = 100 * Math.Round(assignedToOthersCount / (assignedToOthersCount + totalSsrsWorkedOn), 4),
//AverageReactionTimeMinutes = averageReactionTimeMinutes,
AverageTimeToCompleteHours = x.Where(y => y.CloseDate != null && y.Time_1 != null && y.Time_1 != DateTime.MinValue).Select(z => (z.CloseDate - z.Time_1).TotalHours).Average(),
//Close = x.Where(y => y.CloseDate != null && y.Time_1 != null).Select(z => (z.CloseDate)),
//Time = x.Where(y => y.CloseDate != null && y.Time_1 != null).Select(z => (z.Time_1)),
MedianDaysRequestOpen = Math.Round(GetMedian(listOfDaysOpenTime), 3),
DaysWorkedPerDateRange = totalDaysWorked,
AveSSRsClosedPerWorkedDay = Math.Round(totalSsrsWorkedOn / totalDaysWorked, 3),
AveHoursLoggedPerRequest = Math.Round((x.Select(y => y.ServiceTechHrs + y.ServiceEngHrs).Sum()) / totalSsrsWorkedOn, 3),
BrokenWiresFixed = brokenWiresFixed,
PercentageBrokenWires = 100 * Math.Round(brokenWiresFixed / totalSsrsWorkedOn, 4),
NPFResults = npfResults,
PercentageNPF = 100 * Math.Round(npfResults / totalSsrsWorkedOn, 4),
};
}).OrderByDescending(x => x.TotalSSRsWorkedOn)
.Dump("Summary");
return;
Sample output, with the duplicate dates evaluated (workingDatesList):
8/1/2017 12:00:00 AM
8/1/2017 12:00:00 AM
8/1/2017 12:00:00 AM
8/2/2017 12:00:00 AM
A couple of comments on the code you posted:
Since you don't ever use the day variable from the outermost loop, simply remove that loop altogether.
Why are you testing whether x.Any(...) within a loop that iterates over y? This seems fundamentally flawed.
I can't discern from your problem statement what your data structures are, nor what it is that you are actually trying to do. Your problem statement is currently worded as:
I am trying to search through a group of days, and determine if a worker has worked that day, and get a total of days worked.
It appears you are taking some input called testtech (String) and totalDaysInDateRange (List<DateTime>), then want to find all entries in some data structure x (I can't infer what this is) where String.equalsIgnoreCase(y.AssignerName, testtech) && totalDaysInDateRange.contains(y.Date). Is this interpretation correct?
If so, simply iterate over the entries in whatever your x data structure is, and run the above logic. If this doesn't solve your problem, then please give us more information on the layout of the data structure x and how information about each worker is actually associated with the other data about that worker.
BEGIN EDIT
OK, now that you have provided more information, I think you want to replace the totalDaysInDateRange.ForEach statement with the following:
x.Select(y => y.WorkedDataPointsList).ForEach((wdp) =>
{
if (testtech == wdp.AssignerName && IniDate.Date <= wdp.Date.Date
&& wdp.Date.Date <= EndDate.Date)
{
workingDatesList.Add(wdp.Date.Date);
}
});
After changing your implementation, simply delete totalDaysInDateRange. I also recommend changing the type of workingDatesList to HashSet<DateTime>, since you don't seem to care about duplicate dates. Be sure to convert workingDatesList to a list and sort it once the loop is complete if you want the dates printed in chronological order.
I'm using Entity Framework. The class below represents a table from the database.
public partial class TermBucket
{
public short ID { get; set; }
public byte Period { get; set; }
public byte Min { get; set; }
public byte Max { get; set; }
}
The PK is ID and Period, so there can be multiple ID's in the table.
When querying the entity, I have at my disposal the period + time (the number associated with the period). Period is set up as an enum, so I would use it as:
Time: 3
Period: 3 (Days)
What I want to do is to find the bucket that matches by requirements and then get all entries for that bucket. Here's what I currently do:
Step 1: Get the ID
return r.Find() // this is my repository
.AsNoTracking()
.Where(x => (int)tp.Period == x.Period && tp.Time >= x.Min && tp.Time <= x.Max)
.Select(x => x.ID)
.Single();
Step 2: Get all entries for the ID, using the retrieved ID
return r.Find()
.AsNoTracking()
.Where(x => x.ID == ID );
So, there are 2 distinct queries, but is it possible to retrieve this data in one go?
Can't you just combine them?
return r.Find()
.AsNoTracking()
.Where(x => x.ID == r.Find()
.AsNoTracking()
.Where(x => (int)tp.Period == x.Period && tp.Time >= x.Min && tp.Time <= x.Max)
.Select(x => x.ID)
.Single());
You can do it using join. Example
public partial class TermBucket
{
public short ID { get; set; }
public byte Period { get; set; }
public byte Min { get; set; }
public byte Max { get; set; }
}
static void Main(string[] args)
{
List<TermBucket> l = new List<TermBucket>();
l.Add(new TermBucket() { ID = 1, Period = 3, Min = 10, Max = 14 });
l.Add(new TermBucket() { ID = 1, Period = 4, Min = 10, Max = 13 });
l.Add(new TermBucket() { ID = 1, Period = 5, Min = 100, Max = 25 });
l.Add(new TermBucket() { ID = -1, Period = 3, Min = 10, Max = 12 });
int period = 3;
int minV = 10;
int maxV = 13;
var res = from e in l
join e2 in l on e.ID equals e2.ID
where e.Period == period && minV >= e.Min && maxV <= e.Max
select e2;
foreach (var r in res)
{
Console.WriteLine(r.ID + " " + r.Period);
}
Console.ReadLine();
}
Will output
1 3
1 4
1 5
Yes, it is:
return r.Find()
.AsNoTracking()
.Where(x => x.ID == r.Find()
.AsNoTracking()
.Where(x => (int)tp.Period == x.Period && tp.Time >= x.Min && tp.Time <= x.Max)
.Select(x => x.ID)
.Single());
But I would recommend breaking it up into two queries, as you already have it, to handle the case where the first query returns no results. (As it is, currently, .Single() will throw an exception if .Select() is empty).
I'm looking to fill an object model with the count of a linq-to-sql query that groups by its key.
The object model looks somewhat like this:
public class MyCountModel()
{
int CountSomeByte1 { get; set; }
int CountSomeByte2 { get; set; }
int CountSomeByte3 { get; set; }
int CountSomeByte4 { get; set; }
int CountSomeByte5 { get; set; }
int CountSomeByte6 { get; set; }
}
This is what I have for the query:
var TheQuery = from x in MyDC.TheTable
where ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7
group x by x.SomeByte into TheCount
select new MyCountModel()
{
CountSomeByte1 = TheCount.Where(TheCount => TheCount.Key == 1)
.Select(TheCount).Count(),
CountSomeByte2 = TheCount.Where(TheCount => TheCount.Key == 2)
.Select(TheCount).Count(),
.....
CountSomeByte6 = TheCount.Where(TheCount => TheCount.Key == 6)
.Select(TheCount).Count(),
}.Single();
ListOfRecordIDs is list of longs that's passed in as a parameter. All the CountSomeByteN are underlined red. How do you do a count of grouped elements with the group's key mapped to an object model?
Thanks for your suggestions.
The select is taking each element of your group and projecting them to identical newly created MyCountModels, and you're only using one of them. Here's how I'd do it:
var dict = MyDC.TheTable
.Where(x => ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7)
.GroupBy(x => x.SomeByte)
.ToDictionary(grp => grp.Key, grp => grp.Count());
var result = new MyCountModel()
{
CountSomeByte1 = dict[1];
CountSomeByte2 = dict[2];
CountSomeByte3 = dict[3];
CountSomeByte4 = dict[4];
CountSomeByte5 = dict[5];
CountSomeByte6 = dict[6];
}
EDIT: Here's one way to do it in one statement. It uses an extension method called Into, which basically works as x.Into(f) == f(x). In this context, it can be viewed as like a Select that works on the whole enumerable rather than on its members. I find it handy for eliminating temporary variables in this sort of situation, and if I were to write this in one statement, it's probably how I'd do it:
public static U Into<T, U>(this T self, Func<T, U> func)
{
return func(self);
}
var result = MyDC.TheTable
.Where(x => ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7)
.GroupBy(x => x.SomeByte)
.ToDictionary(grp => grp.Key, grp => grp.Count())
.Into(dict => new MyCountModel()
{
CountSomeByte1 = dict[1];
CountSomeByte2 = dict[2];
CountSomeByte3 = dict[3];
CountSomeByte4 = dict[4];
CountSomeByte5 = dict[5];
CountSomeByte6 = dict[6];
});
Your range variable is not correct in the subqueries:
CountSomeByte6 = TheCount.Where(TheCount => TheCount.Key == 6)
.Select(TheCount).Count(),
In method notation you don't need the extra select:
CountSomeByte6 = TheCount.Where(theCount => theCount.Key == 6).Count(),
If you want to use it anyway:
CountSomeByte6 = TheCount.Where(theCount => theCount.Key == 6).Select(theCount => theCount).Count(),