I am tracking fuel for trucks.
List<FuelLightTruckDataSource> data = new List<FuelLightTruckDataSource>();
using (SystemContext ctx = new SystemContext())
{
List<FuelLightTruckDataSource> dataTransfers
= ctx.FuelTransfer
.Where(tr => DbFunctions.TruncateTime(tr.Date) >= from.Date && DbFunctions.TruncateTime(tr.Date) <= to.Date
//&& tr.ToAsset.AssignedToEmployee.Manager
&& tr.ToAsset.AssignedToEmployee != null
&& tr.ToAsset.AssetType.Code == "L"
&& tr.Volume != null)
//&& (tr.FuelProductType.FuelProductClass.Code == "GAS" || tr.FuelProductType.FuelProductClass.Code == "DSL"))
.GroupBy(tr => new { tr.ToAsset, tr.Date, tr.FuelTankLog.FuelProductType.FuelProductClass, tr.FuelTankLog.FuelCard.FuelVendor,tr.Volume, tr.ToAssetOdometer })
.Select(g => new FuelLightTruckDataSource()
{
Asset = g.FirstOrDefault().ToAsset,
Employee = g.FirstOrDefault().ToAsset.AssignedToEmployee,
ProductClass = g.FirstOrDefault().FuelTankLog.FuelProductType.FuelProductClass,
Vendor = g.FirstOrDefault().FuelTankLog.FuelCard.FuelVendor,
FillSource = FuelFillSource.Transfer,
Source = "Slip Tank",
City = "",
Volume = g.FirstOrDefault().Volume.Value,
Distance = g.FirstOrDefault().ToAssetOdometer,
Date = g.FirstOrDefault().Date,
})
.ToList();
After my query, I need to calculate the consumption rate and distance traveled. "Result" will be a collection of entries including "consumptionRate" and "Distance" and it matches the query above.
// Get consumption rate data for each asset
foreach (int assetId in assetIds)
{
FuelConsumptionRateQueryResult result = FuelConsumptionRateQueryService.Get(assetId, from, to, AssetCounterType.Odometer);
result.Entries.ToList();
}
My question is how do I get the result of my foreach loop and add them to my previous query/list, so they can show up in the report?
Your issue seems to be due to the fact that you're declaring result as a new variable at each iteration of the foreach loop, causing result to be overwritten. Additionally, result isn't in scope once you finish with the foreach loop.
Instead, you probably want something closer to:
var allResults = new List<T>; //where T is whatever object type this is (FuelConsumptionRateQueryResult?)
foreach (int assetId in assetIds)
{
//this is a new variable at each iteration of the loop
FuelConsumptionRateQueryResult result = FuelConsumptionRateQueryService.Get(assetId, from, to, AssetCounterType.Odometer);
allResults.Add(result); //add the current result to your list (outside the scope of this loop)
}
Basically, declare your variable outside of the loop and Add each individual result to the allResults list.
Create the additional data by a LINQ statement and concatenate both lists:
var results = assetIds.Select(id => FuelConsumptionRateQueryService
.Get(id, from, to, AssetCounterType.Odometer);
dataTransfers = dataTransfers.Concat(results);
Related
I want to take a list of items from a table, apply grouping logic(grouping by assignment type, start and end time), and then convert back to starting type.
IEnumerable<Assignment> assignments = UnitOfWork.AssignmentRepository.Get(filter: a => a.Start.CompareTo(start) >= 0
&& a.End.CompareTo(end) <= 0,
orderBy: o => o.OrderBy(a => a.Start));
List<Assignment> pre_alerts = new List<Assignment>();
foreach (Assignment a in assignments)
{
foreach (AssignmentType t in assignmentTypes)
{ // determine if the assignment's type has a minimum required
if (a.AssignmentTypeID == t.AssignmentTypeID && t.Minimum>0)
{
// get the minimum assignments requierd
int min_required = t.Minimum;
DateTime s = a.Start;
DateTime e = a.End;
AssignmentType type = a.AssignmentType;
IEnumerable<Assignment> minimum_assignments = UnitOfWork.AssignmentRepository.Get(filter: m => m.Start.CompareTo(s) >= 0 && m.End.CompareTo(e) <= 0 && m.AssignmentTypeID == a.AssignmentTypeID, orderBy: o => o.OrderBy(m => m.Start));
int min_actual = minimum_assignments.Count();
if (min_actual < min_required)
{
// add a single alert for time range and assignment type
pre_alerts.Add(a);
}
}
}
}
var alerts = pre_alerts.GroupBy(x => new Assignment{ x.AssignmentTypeID, x.Start, x.End }).Select(k => k);
how do I convert alerts to IEnumerable<Assignment> ?
When using Select you are already returning it as an IEnumerable. If you want it to be of your type then:
var alerts = pre_alerts.GroupBy(x => new Assignment{ x.AssignmentTypeID, x.Start, x.End }).Select(k => k.Key);
The result of a GroupBy is an IGrouping so by Selecting just k you are returning an IEnumerable<IGrouping<Assignment...>>
But it feels to me that if that is the case then there is no reason to use the GroupBy but just Select to create the assignments and then distinct
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;
}
What I want to be able to do is search the searchValues list for duplicate itemId's, and when I find them, place the single string value into the string values array.
The SearchValue object:
public class SearchValue<TItemId>
{
public TItemId ItemId { get; set; }
public string Value { get; set; }
public string[] Values { get; set; }
}
My test search values after init looks like this:
searchValues[0]
.ItemId == 16
.Value == "2"
searchValues[1]
.ItemId == 16
.Value == "3"
searchValues[2]
.ItemId == 15
.Value == "6"
searchValues[3]
.ItemId == 15
.Value == "3"
searchValues[4]
.ItemId == 5
.Value == "Vertonghen"
I'd like my final result to look like this:
searchValues[0]
.ItemId == 16
.Values == "2,3"
searchValues[1]
.ItemId == 15
.Values == "6,3"
searchValues[2]
.ItemId == 5
.Value == "Vertonghen"
I'd really want to do this using LINQ. I've managed to create another List of SearchValue thusly:
List<SearchValue<Byte>> duplicateSearchItems = (from x in searchValues
group x by x.ItemId into grps
orderby grps.Key
where grps.Count() > 1
select grps).SelectMany(group => group).ToList();
...but getting the value into the values array is giving me trouble. Ideally if the LINQ could return a single list that contains the duplicate records transmogrified into the array with the non-duplicates intact would be best. Probably a nested query of some sort? I'm stumped. Any ideas?
Thanks in advance!
why don't you use Dictionary
Dictionary<int,string> d = new Dictionary<int,string>();
foreach(var x in searchValues)
{
if(d.ContainsKey(x.ItemId))
d[x.ItemId] = string.Format("{0},{1}",d[x.ItemId],x.Value);
else
d.Add(x.ItemId,x.Value);
}
on the end simply iterate throug the Dictionary
foreach(var entry in d)
{
ConsoleWriteline(entry.Key+" : "+entry.Value);
}
Linq alone cannot be used to modify the original list or to modify the items in the list. However, you could do this to create a new list:
List<SearchValue<Byte>> results =
(from x in searchValues
group x by x.ItemId into g
select new SearchValue<Byte>()
{
ItemId = g.Key,
Value = g.Value.First().Value,
Values = g.Value.Select(i => i.Value).ToArray(),
}
.ToList();
Or in fluent syntax:
List<SearchValue<Byte>> results = searchValues
.GroupBy(x => x.ItemId)
.Select(g => new SearchValue<Byte>()
{
ItemId = g.Key,
Value = g.Value.First().Value,
Values = g.Value.Select(i => i.Value).ToArray(),
})
.ToList();
However, depending on your situation, an ILookup may be more appropriate for you:
var results = searchValues.ToLookup(x => x.ItemId, x => x.Value);
Console.Write(String.Join(", ", results[16])); // 2, 16
I don't think LINQ would provide you with the best solution here. Similar to Nikola, I'd use a Dictionary. If you aren't married to your SearchValue data type, you can avoid the second loop that just pushes your data back into your type. A Dictionary> would work here.
var searchValues = new List<SearchValue<int>>();
var distinctItemIds = new Dictionary<int, List<string>>();
foreach (var item in searchValues)
{
if (!distinctItemIds.ContainsKey(item.ItemId))
{
distinctItemIds.Add(item.ItemId, new List<string>());
}
// Add the value
distinctItemIds[item.ItemId].Add(item.Value);
}
// Put values back into your data object
var finishedValues = new List<SearchValue<int>>();
foreach (var keyValuePair in distinctItemIds)
{
finishedValues.Add(new SearchValue<int>()
{
ItemId = keyValuePair.Key,
Values = keyValuePair.Value.ToArray()
});
}
I managed to work it out using LINQ.
// Get a new list of unique items to add our duplicated items to
List<SearchValue<Byte>> finalSearchItems = (from x in searchValues
group x by x.ItemId into grps
orderby grps.Key
where grps.Count() == 1
select grps).SelectMany(group => group).ToList();
byte[] duplicateIds = (from x in searchValues
group x by x.ItemId into grps
where grps.Count() > 1
select grps.Key).ToArray();
// Smash the string 'Value' into 'Values[]'
foreach (byte id in duplicateIds)
{
SearchValue<Byte> val = new SearchValue<byte>();
val.ItemId = id;
// Smash
val.Values = (from s in searchValues
where s.ItemId == id
select s.Value).ToArray();
finalSearchItems.Add(val);
}
All I want to do is declare var place correctly so it is still in scope once I get to the foreach loop. I'm assuming I need to declare it before the if statement for connections. Is this a correct assumption and if so how do I declare it? Thanks!
using (var db = new DataClasses1DataContext())
{
if (connections == "Connections")
{
var place = (from v in db.pdx_aparts
where v.Latitude != null && v.Region == region && v.WD_Connect >= 1
select new
{
locName = v.Apartment_complex.Trim().Replace(#"""", ""),
latitude = v.Latitude,
longitude = v.Longitude
}).Distinct().ToArray();
}
else
{
var place = (from v in db.pdx_aparts
where v.Latitude != null && v.Region == region && ((v.WD_Connect == null) || (v.WD_Connect == 0))
select new
{
locName = v.Apartment_complex.Trim().Replace(#"""", ""),
latitude = v.Latitude,
longitude = v.Longitude
}).Distinct().ToArray();
}
foreach (var result in place)
....
You can create an array with a single entry whose value you ignore later:
// Note: names *and types* must match the ones you use later on.
var place = new[] { new { locName = "", latitude = 0.0, longitude = 0.0 } };
if (connections = "Connections")
{
// Note: not a variable declaration
place = ...;
}
else
{
place = ...;
}
This works because every use of anonymous types using properties with the same names and types, in the same order, will use the same concrete type.
I think it would be better to make the code only differ in the parts that it needs to though:
var query = v.db.pdx_aparts.Where(v => v.Latitude != null && v.Region == region);
query = connections == "Connections"
? query.Where(v => v.WD_Connect >= 1)
: query.Where(v => v.WD_Connect == null || v.WD_Connect == 0);
var places = query.Select(v => new
{
locName = v.Apartment_complex
.Trim()
.Replace("\"", ""),
latitude = v.Latitude,
longitude = v.Longitude
})
.Distinct()
.ToArray();
Here it's much easier to tell that the only part which depends on the connections value is the section of the query which deals with WD_Connect.
You could convert the if to a ?:.
var place = connections == "Connections" ? monsterQuery1 : monsterQuery2;
I do not think this is a good solution because your queries are too big (unreadable).
It would be much better if you introduced a named class that you use instead of the anonymous type. R# does that for you in a "light bulb menu" refactoring.
you could just use the 1 query since they are pretty much the same, and just add the extra condition in the where clause
var place = (from v in db.pdx_aparts
where v.Latitude != null && v.Region == region
&& connections == "Connections"
? v.WD_Connect >= 1
: ((v.WD_Connect == null) || (v.WD_Connect == 0))
select new
{
locName = v.Apartment_complex.Trim().Replace(#"""", ""),
latitude = v.Latitude,
longitude = v.Longitude
}).Distinct().ToArray();
foreach (var result in place)
....
I have a query that sums and aggregates alot of data something like this:
var anonType = from x in collection
let Cars = collection.Where(c=>c.Code == "Cars")
let Trucks = collection.Where(c=>c.Code == "Trucks")
select new {
Total = collection.Sum(v=>v.Amount),
CarValue = Cars.Sum(v=>v.Amout),
TruckValue = Trucks.Sum(v=>v.Amount),
CarCount = Cars.Count(),
TruckCount = Trucks.Count()
};
I find it really weird that I have to declare the range variable x, especially if I'm not using it. So, am I doing something wrong or is there a different format I should be following?
I could be wrong, but from your usage, I don't think you want to do a traditional query expression syntax query with your collection anyway, as it appears you are only looking for aggregates. The way you have it written, you would be pulling multiple copies of the aggregated data because you're doing it for each of the items in the collection. If you wished, you could split your query like this (sample properties thrown in)
var values = collection.Where(c => c.Code == "A");
var anonType = new
{
Sum = values.Sum(v => v.Amount),
MinimumStartDate = values.Min(v => v.StartDate),
Count = values.Count()
};
You declare a range variable no matter the looping construct:
foreach(var x in collection)
or
for(var index = 0; index < collection.Count; index++)
or
var index = 0;
while(index < collection.Count)
{
//...
index++;
}
Queries are no different. Just don't use the variable, it doesn't hurt anything.
So, am I doing something wrong?
Your query is not good. For each element in the collection, you are enumerating the collection 5 times (cost = 5*n^2).
Is there a different format I should be following?
You could get away with enumerating the collection 5 times (cost = 5n).
IEnumerable<X> cars = collection.Where(c => c.Code == "Cars");
IEnumerable<X> trucks = collection.Where(c => c.Code == "Trucks");
var myTotals = new
{
Total = collection.Sum(v => v.Amount),
CarValue = cars.Sum(v => v.Amount),
TruckValue = trucks.Sum(v => v.Amount,
CarCount = cars.Count(),
TruckCount = trucks.Count()
};