moq callback makes linq throw exception - c#

I am using Moq and EF6.
I need to query a table and if there are less than 5 dates in the table I need to add more.
So, I do that in my code:
public void FillPartyList(ObservableCollection<Party> argPartyList)
{
argPartyList.Clear();
using (IPartyTrackerDataAccess localDb = Locator.Resolve<IPartyTrackerDataAccess>())
{
InitializeDates(localDb);
var localList = (from tempList in localDb.Parties
where tempList.PartyDate >= DateTime.Now.Date
select tempList);
foreach (Party currentParty in localList)
{
argPartyList.Add(currentParty);
}
}
}
Inside "InitializeDates" I query the database to see how many dates are there and then add enough to make 5. There are rules around creating the dates [e.g. only on Fri or Sat, etc] so this is not trivial. Then, when the method comes back I get the full list from the database and add it to the parameter.
My problem comes when I try to test this. Here's my test.
public void TestInitializeDates()
{
//Expected data
int callCount = 0;
int addParty = 0;
int saveChanges = 0;
ObservableCollection<Party> PartyList = new ObservableCollection<Party>();
//Mock data
IQueryable<Party> partyData = new List<Party>().AsQueryable();
IQueryable<SystemSetting> settingsData = new List<SystemSetting>().AsQueryable();
//Setup mock
Mock<PartyTrackerEntities> mockContext = SetupDbContext();
SetupApplicationSettings(mockContext, settingsData);
SetupPartyDbSet(mockContext, partyData);
Locator.Register<IApplicationSettings, ApplicationSettings>();
//If I keep the following line I get an exception when querying the "database" for parties.
mockContext.Setup(m => m.Parties.Add(It.IsAny<Party>())).Callback(() => addParty = callCount++);
mockContext.Setup(m => m.SaveChanges()).Callback(() => saveChanges = callCount++);
//Run tests
PartyTrackerModel localModel = new PartyTrackerModel();
localModel.FillPartyList(PartyList);
Assert.AreEqual(1, callCount);
Assert.AreEqual(0, saveChanges);
}
private Mock<DbSet<Party>> SetupPartyDbSet(Mock<PartyTrackerEntities> argDbContext, IQueryable<Party> argPartyData)
{
//Mock objects
Mock<DbSet<Party>> mockPartySet = new Mock<DbSet<Party>>();
mockPartySet.As<IQueryable<Party>>().Setup(m => m.Provider).Returns(argPartyData.Provider);
mockPartySet.As<IQueryable<Party>>().Setup(m => m.Expression).Returns(argPartyData.Expression);
mockPartySet.As<IQueryable<Party>>().Setup(m => m.ElementType).Returns(argPartyData.ElementType);
mockPartySet.As<IQueryable<Party>>().Setup(m => m.GetEnumerator()).Returns(argPartyData.GetEnumerator());
argDbContext.Setup(m => m.Parties).Returns(mockPartySet.Object);
return mockPartySet;
}
EDIT: I got distracted and didn't finish the question.
Everything works fine until I add the setup that increments a counter, then the Linq in InitializeDates() fails.
How do I increment the counter and leave the DbSet<> mocking properly? How do I query the "database" and still count the calls to Add? If I setup the Context.Parties.Add() to count calls the linq query blows up.
What am I doing wrong?
EDIT: Adding InitializeDates(). Note: None of this has a problem until I add the Moq.Callback.
private static void InitializeDates(IPartyTrackerDataAccess argLocalDb)
{
IApplicationSettings localAppSettings = Locator.Resolve<IApplicationSettings>();
//using (PartyTrackerEntities LocalDb = new PartyTrackerEntities())
{
//TODO: Move this code to the PartyTrackerDb class.
//Technically this would communicate with the database and
//make sure there are at least 5 parties in the future.
var SqlPartyList = from LinqList in argLocalDb.Parties
where LinqList.PartyDate >= DateTime.Now.Date
select LinqList;
List<Party> PartyList;
try
{
PartyList = SqlPartyList.OrderByDescending(argOrder => argOrder.PartyDate).Take(5).ToList();
}
catch (SqlException)
{
//MessageBox.Show("Cannot connect to database. Please ensure that SQLServer is running and accessable from this machine.");
throw;
}
if (PartyList.Count < 5)
{
DateTime MaxPartyDate = PartyList.Count <= 0
? DateTime.Now.Date.AddDays(-1)
: PartyList.Max(argMaxDate => argMaxDate.PartyDate);
//<= 4 because PartyList.Count is zero-based so this is 5 parties.
for (int ShortDayCount = PartyList.Count; ShortDayCount <= 4; ShortDayCount++)
{
int dateOffset;
decimal coupleDonation;
decimal singleMaleDonation;
decimal singleFemaleDonation;
decimal sponsoredMaleDonation;
if (MaxPartyDate.DayOfWeek == DayOfWeek.Friday) //If the last party in the list is on a Friday, then we need to add a Saturday
{
//Get the Donation amounts
//Get Saturday Donations
coupleDonation = localAppSettings.SatCoupleDonation;
singleMaleDonation = localAppSettings.SatSingleMaleDonation;
singleFemaleDonation = localAppSettings.SatSingleFemaleDonation;
sponsoredMaleDonation = localAppSettings.SatSponsoredMaleDonation;
//Add Sat
dateOffset = 1;
}
else //Otherwise we need to add a Friday
{
//Get the Donation amounts
//Get Friday Donations
coupleDonation = localAppSettings.FriCoupleDonation;
singleMaleDonation = localAppSettings.FriSingleMaleDonation;
singleFemaleDonation = localAppSettings.FriSingleFemaleDonation;
sponsoredMaleDonation = localAppSettings.FriSponsoredMaleDonation;
if (MaxPartyDate.DayOfWeek == DayOfWeek.Saturday)
{
dateOffset = 6;
}
else
{
dateOffset = 5 - (int)MaxPartyDate.DayOfWeek;
}
}
Party NewParty = new Party
{
PartyDate = MaxPartyDate.AddDays(dateOffset),
CoupleDonation = coupleDonation,
SingleMaleDonation = singleMaleDonation,
SingleFemaleDonation = singleFemaleDonation,
SponsoredMaleDonation = sponsoredMaleDonation
};
argLocalDb.Parties.Add(NewParty);
MaxPartyDate = NewParty.PartyDate;
}
argLocalDb.SaveChanges();
}
}
}

Related

Match data from two list<T> where date from one list falls between dates from second list

I have a list of objects (Pulled from SQL DB) with a TransactionDate for each object “alarmHistoryList”.
I have another list of objects (Pulled from SQL DB) and each object has a StartDate a FinishDate and an ID “RunLogList”.
There will be a Many to One relationship where “List1” will be the many and “RunLogList” the one. Each Run may have many Alarms.
I want every object in “alarmHistoryList” returned with the ID of the object in “RunLogList” where the TransactionDate fall between the StartDate and the FinishDate.
private void MatchRunData()
{
foreach (var alarm in _alarmHistoryList)
{
var AlarmTransTime = alarm.TransactionTime;
var filteredData = _FARunLogList.Where(t =>
t.TrackInTime > AlarmTransTime && t.TrackOutTime < AlarmTransTime);
}
}
Run logs with alarms matching the run log time window:
var runLogAlarms = new Dictionary<RunLog, IList<Alarm>>();
foreach (var alarm in _alarmHistoryList)
{
var alarmTransTime = alarm.TransactionTime;
var filteredData = _FARunLogList
.Where(t => t.TrackInTime > alarmTransTime && t.TrackOutTime < alarmTransTime)
.ToList();
foreach (var runLog in filteredData)
{
if (runLogAlarms.TryGetValue(runLog, out var alarmsValue))
{
alarmsValue.Add(alarm);
}
else
{
runLogAlarms[runLog] = new List<Alarm> { alarm };
}
}
}
I came up with an answer before Prolog that works. I am sure the answer Prolog gave works as well and is cleaner but I am posting my answer since it is the one I will be using.
private void MatchRunData()
{
foreach (var alarm in _alarmHistoryList)
{
var AlarmTransTime = alarm.TransactionTime;
foreach (var run in _FARunLogList)
{
var TrackInTime = run.TrackInTime;
var TrackOutTime = run.TrackOutTime;
var ID = run.LogId;
if (AlarmTransTime > TrackInTime && AlarmTransTime < TrackOutTime)
{
_MergedalarmHistoryList.Add
(new AlarmHistoryDefinition()
{ AlarmDesc = alarm.AlarmDesc, AlarmID = alarm.AlarmID, ToolID = alarm.ToolID,
TransactionTime = alarm.TransactionTime, GlobalToolID = alarm.GlobalToolID,
RUnLogID = run.LogId });
}
}
_MergedalarmHistoryList.Add(new AlarmHistoryDefinition()
{ AlarmDesc = alarm.AlarmDesc, AlarmID = alarm.AlarmID, ToolID = alarm.ToolID,
TransactionTime = alarm.TransactionTime, GlobalToolID = alarm.GlobalToolID,
RUnLogID = 00000 });
}
}
Can try this
private void MatchRunData()
{
foreach (var alarm in _alarmHistoryList)
{
var filteredData = _FARunLogList.Where(t =>
t.TrackInTime > alarm.TransactionTime && t.TrackOutTime < alarm.TransactionTime);
alarm.RunLogListId = filteredData.RunLogListId;
}
}

LINQ in SQL - How to sort data and separate by months

So I got database with very bad architecture :(
So now I need to write logic for get from table data and I need create List of my class objects from this data - but separate its by month
I have table, and there I have column Date ( 2016-01-12 00:00:00.000 this is format)
So now I want to get data - group by date (because if I have a few record for 1 day - I want to get summ of some properties for 1 day )
ANd I need to create array(List) :
This what I try:
public List<DriverPayroll> GetAllPayrolls(int pageNumber, int showOnPage, string DriverNumber)
{
Database db = new Database();
var loads = db .TableA.Where(i => i.Driver == DriverNumber).Skip(pageNumber * showOnPage).GroupBy(o => o.Data);
List<DriverPayroll> payrols = new List<DriverPayroll>();
DriverPayroll payRoll = new DriverPayroll();
int month = loads.First().First().Data.Month;
foreach (var load in loads)
{
if(load.First().Data.Month != month)
{
payrols.Add(payRoll);
payRoll = new DriverPayroll();
}
DriverWordDay workDay = new DriverWordDay();
workDay.Date = load.First().Data;
var tt = workDay.Date.Month;
LoadData newLoad = new LoadData();
foreach (var data in load)
{
newLoad.Stops += data.Stops;
newLoad.Base += data.Base;
newLoad.Attempts += data.Stops_Atempts;
newLoad.Total += data.Total;
}
workDay.Loads.Add(newLoad);
payRoll.WorkDays.Add(workDay);
}
return payrols;
}
If I understand correct - here I do very stupid thing:
if(d.First().Data.Month != month)
{
classA .Add(classB);
classB = new ClassB();
}
Because I just replace pointer in memory, right?
And also I don`t really like how it all looks.
Can you help me improve this code?
Thank you

How to get gap in date ranges from a period of time

I have an initial and a final date range = 1/1/2015 - 1/30/2015
I have these date ranges that represent dates of unavailability.
1/5/2015 - 1/10/2015
1/15/2015 - 1/20/2015
1/22/2015 - 1/28/2015
I want this output, mainly the dates of availability from the main range:
A: 1/1/2015 - 1/4/2015
B: 1/11/2015 - 1/14/2015
C: 1/21/2015 - 1/21/2015
D: 1/29/2015 - 1/30/2015
I tried to generate a sequential date range like this in order to get the exception dates with Except() but I think I'm complicating the thing.
//dtStartDate = 1/1/2015
//dtEndDate = 1/30/2015
var days = (int)(dtEndDate - dtStartDate).TotalDays + 1;
var completeSeq = Enumerable.Range(0, days).Select(x => dtStartDate.AddDays(x)).ToArray();
How can I get the gap of date ranges from period of time.
I other words how can I get the A, B, C and D from this picture
http://www.tiikoni.com/tis/view/?id=ebe851c
If these dates overlap, they must not be considered only where is a gap.
----------UPDATE-----------
I think if I do this:
var range = Enumerable.Range(0, (int)(1/10/2015 - 1/5/2015).TotalDays + 1).Select(i => 1/5/2015.AddDays(i));
var missing = completeSeq.Except(range).ToArray();
for each date range I will have the exclusion of each date range given but still cannot get the gap!
I saw your question in my morning today and really liked it, but was busy the whole day. So, got a chance to play with your question and believe me I enjoyed it. Here is my code:-
DateTime startDate = new DateTime(2015, 1, 1);
DateTime endDate = new DateTime(2015, 1, 30);
int totalDays = (int)(endDate - startDate).TotalDays + 1;
availability.Add(new Availability { StartDate = endDate, EndDate = endDate });
var result = from x in Enumerable.Range(0, totalDays)
let d = startDate.AddDays(x)
from a in availability.Select((v, i) => new { Value = v, Index = i })
where (a.Index == availability.Count - 1 ?
d <= a.Value.StartDate : d < a.Value.StartDate)
&& (a.Index != 0 ? d > availability[a.Index - 1].EndDate : true)
group new { d, a } by a.Value.StartDate into g
select new
{
AvailableDates = String.Format("{0} - {1}",g.Min(x => x.d),
g.Max(x => x.d))
};
This, definitely need explanation so here it is:-
Step 1: Create a range of dates from Jan 01 till Jan 30 using Enumerable.Range
Step 2: Since after the second unavailable date range, we need to limit the dates selected from last endate till current object startdate, I have calculated index so that we can get access to the last enddate.
Step 3: Once we get the index, all we need to do is filter the dates except for first date range since we didn't have last object in this case.
Step 4: For the last item since we don't have the max range I am adding the endDate to our unavailable list (hope this makes sense).
Here is the Working Fiddle, if you get confused just remove group by and other filters and debug and see the resulting output it will look fairly easy :)
using System;
using System.Collections.Generic;
using System.Linq;
public static class Program {
public static void Main() {
Tuple<DateTime,DateTime> range=Tuple.Create(new DateTime(2015,1,1),new DateTime(2015,1,30));
Tuple<DateTime,DateTime>[] exclude=new[] {
Tuple.Create(new DateTime(2015,1,5),new DateTime(2015,1,10)),
Tuple.Create(new DateTime(2015,1,15),new DateTime(2015,1,20)),
Tuple.Create(new DateTime(2015,1,22),new DateTime(2015,1,28))
};
foreach(Tuple<DateTime,DateTime> r in ExcludeIntervals(range,exclude)) {
Console.WriteLine("{0} - {1}",r.Item1,r.Item2);
}
}
public static IEnumerable<Tuple<DateTime,DateTime>> ExcludeIntervals(Tuple<DateTime,DateTime> range,IEnumerable<Tuple<DateTime,DateTime>> exclude) {
IEnumerable<Tuple<DateTime,bool>> dates=
new[] { Tuple.Create(range.Item1.AddDays(-1),true),Tuple.Create(range.Item2.AddDays(1),false) }.
Concat(exclude.SelectMany(r => new[] { Tuple.Create(r.Item1,false),Tuple.Create(r.Item2,true) })).
OrderBy(d => d.Item1).ThenBy(d => d.Item2); //Get ordered list of time points where availability can change.
DateTime firstFreeDate=default(DateTime);
int count=1; //Count of unavailability intervals what is currently active. Start from 1 to threat as unavailable before range starts.
foreach(Tuple<DateTime,bool> date in dates) {
if(date.Item2) { //false - start of unavailability interval. true - end of unavailability interval.
if(--count==0) { //Become available.
firstFreeDate=date.Item1.AddDays(1);
}
} else {
if(++count==1) { //Become unavailable.
DateTime lastFreeDate=date.Item1.AddDays(-1);
if(lastFreeDate>=firstFreeDate) { //If next unavailability starts right after previous ended, then no gap.
yield return Tuple.Create(firstFreeDate,lastFreeDate);
}
}
}
}
}
}
ideone.com
Got a little oopy...
public class DateRange
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public bool HasStart
{
get { return Start != DateTime.MinValue; }
}
public bool IsInRange(DateTime date)
{
return (date >= this.Start && date <= this.End);
}
public List<DateRange> GetAvailableDates(DateRange excludedRange)
{
return GetAvailableDates(new List<DateRange>(){excludedRange});
}
public List<DateRange> GetAvailableDates(List<DateRange> excludedRanges)
{
if (excludedRanges == null)
{
return new List<DateRange>() { this };
}
var list = new List<DateRange>();
var aRange = new DateRange();
var date = this.Start;
while (date <= this.End)
{
bool isInARange = excludedRanges.Any(er => er.HasStart && er.IsInRange(date));
if (!isInARange)
{
if (!aRange.HasStart)
{
aRange.Start = date;
}
aRange.End = date;
}
else
{
if (aRange.HasStart)
{
list.Add(aRange);
aRange = new DateRange();
}
}
date = date.AddDays(1);
}
if (aRange.HasStart)
{
list.Add(aRange);
}
return list;
}
}

How to remove first element from linq query

Im trying to consolidate a list of records of in and out times per day to the minimum number of records possible.
What i have done so far, is grouped up the lines into the groups they need to be in, and put the in and out times in a list for each day.
then i want to process the lists and add the first set of in and out lines onto a single line, then process the next entry and either create a new line or fill in the blanks of the previous line.
The bit im stuck with is removing the first item from the linq result after i have processed it.
happy to look at doing it a different way.
here is what i have:
List<LoginRecordLine> condensedLoginRecordLines = new List<LoginRecordLine>();
List<LoginRecordLine> currentLoginRecordLines = GetLoginRecordsForLoginRecordReport(lowerDate, upperDate, sageDatabaseID, loggedInUserID);
var groupedLines = from LoginRecordLine line in currentLoginRecordLines
group line by new { line.TimesheetID, line.WorkPatternPayRateID } into g
select new
{
Lines = g,
TimesheetID = g.Key.TimesheetID,
PayRateID = g.Key.WorkPatternPayRateID
};
foreach (var g in groupedLines)
{
var monTimes = from line in g.Lines
orderby line.MonTimeIn ascending
where line.MonTimeSpan != TimeSpan.Zero
select new
{
TimeIn = line.MonTimeIn,
TimeOut = line.MonTimeOut,
Timesheet = line.Timesheet,
PayRate = line.WorkPatternPayRate
};
var tueTimes = //Same as monday
var wedTimes = //Same as monday
var thuTimes = //same as monday
var friTimes = //same as monday
var satTimes = //same as monday
var sunTimes = //same as monday
while (monTimes.Count() != 0 || tueTimes.Count() != 0 || wedTimes.Count() != 0 || thuTimes.Count() != 0 || friTimes.Count() != 0 || satTimes.Count() != 0 || sunTimes.Count() != 0)
{
LoginRecordLine condensedLine = new LoginRecordLine();
if (monTimes.Count() >0)
{
condensedLine.MonTimeIn = monTimes.First().TimeIn;
condensedLine.MonTimeOut = monTimes.First().TimeOut;
condensedLine.Timesheet = monTimes.First().Timesheet;
condensedLine.WorkPatternPayRate = monTimes.First().PayRate;
//*************** REVELANT PART *************/
//remove first item from monday list
}
// tue
// wed
// etc
}
}
return condensedLoginRecordLines;
Update - Working code - before performance changes
List<LoginRecordLine> condensedLoginRecordLines = new List<LoginRecordLine>();
List<LoginRecordLine> currentLoginRecordLines = GetLoginRecordsForLoginRecordReport(lowerDate, upperDate, sageDatabaseID, loggedInUserID);
var groupedLines = from LoginRecordLine line in currentLoginRecordLines
group line by new { line.TimesheetID, line.WorkPatternPayRateID } into g
select new
{
Lines = g,
TimesheetID = g.Key.TimesheetID,
PayRateID = g.Key.WorkPatternPayRateID
};
foreach (var g in groupedLines)
{
var monTimes = (from line in g.Lines
orderby line.MonTimeIn ascending
where line.MonTimeSpan != TimeSpan.Zero
select new
{
TimeIn = line.MonTimeIn,
TimeOut = line.MonTimeOut,
Timesheet = line.Timesheet,
PayRate = line.WorkPatternPayRate
}).ToList();
var tueTimes = //Same as monday
var wedTimes = //Same as monday
var thuTimes = //same as monday
var friTimes = //same as monday
var satTimes = //same as monday
var sunTimes = //same as monday
while (monTimes.Count != 0 || tueTimes.Count != 0 || wedTimes.Count != 0 || thuTimes.Count != 0 || friTimes.Count != 0 || satTimes.Count != 0 || sunTimes.Count != 0)
{
LoginRecordLine condensedLine = new LoginRecordLine();
if (monTimes.Count >0)
{
condensedLine.MonTimeIn = monTimes.First().TimeIn;
condensedLine.MonTimeOut = monTimes.First().TimeOut;
condensedLine.Timesheet = monTimes.First().Timesheet;
condensedLine.WorkPatternPayRate = monTimes.First().PayRate;
condensedLoginRecordLines.Add(condensedLine);
monTimes.RemoveAt(0);
}
//etc
}
}
return condensedLoginRecordLines;
use the List.RemoveAt Method something like myList.RemoveAt(0) will remove the first item of your list
You should revise your algorithm and maybe data structures.
For anonymous types in queries I would add a DayOfWeek property, so the queries will look like:
var monTimes = from line in g.Lines
orderby line.MonTimeIn ascending
where line.MonTimeSpan != TimeSpan.Zero
select new
{
TimeIn = line.MonTimeIn,
TimeOut = line.MonTimeOut,
Timesheet = line.Timesheet,
PayRate = line.WorkPatternPayRate,
WeekDay = DayOfWeek.Monday
};
Then, the final loop will be replaced by something like:
var condensedLoginRecordLines = monTimes
.Concat(tueTimes)
.Concat(wedTimes)
..//etc
.Select(data => new CondensedLine { WeekDay = data.WeekDay, /* here all the properties are initialized */ })
.ToList();
And that's all.
If you still prefer to use the MonInTime, TueInTime, etc. properties, move the creation of CondensedLine into a separate function which applies a switch on the WeekDay and initializes the relevant properties only. In this case you should declare a private class instead the anonymous types you currently use in order to pass the information from method to another.
I came across a similar problem. In my case, the source data was a CSV file parsed into a string and I wanted to remove the header information before processing. I could have used the approach of casting to a list and removing the first entry, but I discovered that it is possible to add a second parameter to the where query that would give you the row number index. This works with both IQueryable and IEnumerable overloads as well.
public static List<string> GetDataLinesFromCSV(string csv)
{
var csvLines = csv.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
var dataLines = csvLines.AsQueryable().Where((x, idx) => idx > 0).ToList();
return dataLines;
}

Condense event list with LINQ

I have a list of log entries in Audit class
public class Audit
{
public DateTime TimeStamp { get; set; }
public string User { get; set; }
public string AuditType { get; set; }
}
so a list might look like this;
20140206 11:29:20 Owen Open
20140206 11:29:21 Owen Close
20140206 11:31:20 Owen Open
20140206 11:32:20 Owen Close
20140206 11:42:20 Owen Open
20140206 11:50:00 Owen Acknowledge
This gives us gaps of 1 second, 1 minute, and 40 seconds. So the longest time it was open was the middle pair for 1 minute, then it was acknowledged at 11:50. I'm looking for the date pair where it was open longes, in this case 1 min.
I know I can process the list in sequentially and find the biggest gap using a TimeSpan but I figure there is a neat LINQ way to do it maybe with groups?
UPDATE It's not pretty, but this is the logic in really expanded walk
var audits = notice.AuditEntries.Where(a => a.User == user);
DateTime? currentOpen = null;
DateTime? bestOpen = null;
DateTime? bestClose = null;
foreach (var audit in audits)
{
if (audit.AuditType == "Open")
{
if (currentOpen.HasValue) continue;
currentOpen = audit.TimeStamp;
}
if (audit.AuditType == "Close" || audit.AuditType == "Acknowledge")
{
if (currentOpen.HasValue)
{
DateTime? currentClose = audit.TimeStamp;
if (!bestOpen.HasValue)
{
bestOpen = currentOpen;
bestClose = currentClose;
}
else
{
if (bestClose.Value.Subtract(bestOpen.Value) > currentClose.Value.Subtract(currentOpen.Value))
{
bestOpen = currentOpen;
bestClose = currentClose;
}
}
currentOpen = null;
}
}
}
I think this will do the trick:
IEnumerable<Audit> audits = ...
var longestAuditsByUser = audits.OrderBy(a => a.Timestamp)
// group by user, since presumably we don't want to match an open from one user with a close from another
.GroupBy(a => a.User)
.Select(userAudits =>
{
// first, align each audit entry with it's index within the entries for the user
var indexedAudits = userAudits.Select((audit, index) => new { audit, index });
// create separate sequences for open and close/ack entries
var starts = indexedAudits.Where(t => t.audit.AuditType == "Open");
var ends = indexedAudits.Where(t => t.audit.AuditType == "Close" || t.audit.AuditType == "Acknowledge");
// find the "transactions" by joining starts to ends where start.index = end.index - 1
var pairings = starts.Join(ends, s => s.index, e => e.index - 1, (start, end) => new { start, end });
// find the longest such pairing with Max(). This will throw if no pairings were
// found. If that can happen, consider changing this Select() to SelectMany()
// and returning pairings.OrderByDescending(time).Take(1)
var longestPairingTime = pairings.Max(t => t.end.Timestamp - t.start.Timestamp);
return new { user = userAudits.Key, time = longestPairingTime };
});
// now that we've found the longest time for each user, we can easily find the longest
// overall time as well
var longestOverall = longestAuditsByUser.Max(t => t.time);
Not tested but should work:
var auditGaps = audits
.GroupBy(a => a.User)
.Select(g => new
{
User = g.Key,
MinOpen = g.Where(a => a.AuditType == "Open").Select(a=> a.TimeStamp).Min(),
MaxClosed = g.Where(a => a.AuditType == "Close").Select(a=> a.TimeStamp).Max(),
MaxAcknowledge = g.Where(a => a.AuditType == "Acknowledge").Select(a=> a.TimeStamp).Max()
})
.Select(x => new
{
x.User,
LargestOpenCloseGap = x.MaxClosed - x.MinOpen,
LargestOpenAcknowledgeGap = x.MaxAcknowledge - x.MinOpen
});

Categories

Resources