LINQ .Where .Max and Writing to File - c#

I have a program which writes its output to log files to folders in the following format:
Car7 Lap1 00-00-21-5290000
Everything is working fine, but I want to append a # to the filename of the fastest lap time for each car. In order to do so, I placed the following LINQ query inside my car object to act on the List property of Car:
public List<Lap> Laps { get; set; } = new List<Lap>();
public TimeSpan FastestLap => Laps.Where(lap => lap.Number is not null and not 0).Min(lap => lap.LapTime);
I'm using the .Where clause as laps can sometimes be null or 0, and when I write to disk things initially appear to be working:
Car8 Lap39 00-01-07-8900000#
However, only 8 cars get written to disk as opposed to the full field of 20 cars. If I remove only the .Where part of my property above, all the cars and car folders write properly, except the Lap 0 files are typically marked as the fastest lap (which makes sense since they contain incomplete times).
My writing to disk method looks like this:
foreach (var car in carList)
{
Directory.CreateDirectory(#$"{outputPath}\{logFileName}\Car{car.Number}");
foreach (var lap in car.Laps)
{
using TextWriter tw = new StreamWriter(#$"{outputPath}\{logFileName}\Car{car.Number}\Car{car.Number} Lap{lap.Number} {lap.LapTime.ToString().Replace(":", "-").Replace(".", "-")}{(lap.LapTime == car.FastestLap ? "#" : "")}.csv");
WriteCsvHeader(tw);
foreach (var telemetryRecord in lap.UniqueTelemetryRecords)
{
WriteCsvLine(tw, lap, telemetryRecord);
}
}
}
Is there a way to prevent this from happening so that all cars and car folders get written to disk?

In this case, use a getter to perform a bit more advanced logic when retrieving the value.
Replace this line:
public TimeSpan FastestLap => Laps.Where(lap => lap.Number is not null and not 0).Min(lap => lap.LapTime);
With this:
public TimeSpan FastestLap
{
get
{
if(Laps.Where(lap => lap.Number is not null and not 0).Count() > 0)
return Laps.Where(lap => lap.Number is not null and not 0).Min(lap => lap.LapTime);
else
return Laps.Min(lap => lap.LapTime);
}
}
This is essentially saying that if any non zero laps exist, get the lowest value. Otherwise, it'll just return the zero value.

One option would be to implement IComparable<T> on your class:
public class Lap : IComparable<Lap>
{
public int CompareTo(Lap other)
{
//if both have nulls or zero they are equal
if((this.Number == null || this.Number == 0) && (other == null || other.Number == null || other.Number == 0) return 0;
// If other is null or the other.Number is null or zero this instance is first in sort order
if (other == null || other.Number == null || other.Number == 0) return -1;
//if this instance has null or zero for the number and other doesn't this instance comes after other
if((this.Number == null || this.Number == 0) && other != null && other.Number != null && other.Number != 0) return 1;
//Comparison depends on this.LapTime to other.LapTime
return LapTime.CompareTo(other.LapTime);
}
......
}
Then you could just call .Sort() on the List<Lap> and your first item in the List will be your fastest lap that is not null or zero. Append the # to the first item in the list and you don't need an extra method in your class.

Related

LINQ search filter logic

I have apartment entity and I want to get apartments based on my filters.
this is my search entity
public class Search
{
public bool isStudio {get;set;}
public bool isNoPlanning {get;set;}
public bool isMultiRoom {get;set;}
public int[] NumberOfRooms {get;set;}
}
this is my current search logic.
var apartments = buildRepost.Get(buildId).Where(condStates =>
(searchModel.NumberOfRooms != null
&& searchModel.NumberOfRooms.Contains(condStates.RoomsCount.ToString())
|| ((searchModel.IsStudio && condStates.IsStudio))
|| ((searchModel.IsNoPlanning && condStates.IsFreePlaning))
|| ((searchModel.IsMultiRoom && condStates.RoomsCount >= 4)));
The problem with this logic is that I got the wrong result when all fields are false and null. For example when isStudio, IsNoplaning and isMultiRoom are false and numberofRooms is null I should have got all apartments but instead, I got an empty array. Any help?
the searchModel.NumberOfRooms != null checker in the where clause causes the problem, you are not mathcing it with any of the condStates properties and the searchModel.Is..... properties
Make your searchModel as a checker in an if statement then build the query from the if searchModel conditions.
var query = buildRepost.Get(buildId).AsQueryable();
if (searchModel.NumberOfRooms != null)
{
query = query.Where(condStates => searchModel.NumberOfRooms.Contains(condStates.RoomsCount.ToString());
}
if (searchModel.IsStudio)
{
query = query.Where(condStates => condStates.IsStudio);
}
if (searchModel.IsNoPlaning)
{
query = query.Where(condStates => condStates.IsFreePlaning)
}
if (searchModel.IsMultiRoom)
{
query = query.Where(condStates => condStates.RoomsCount >= 4)
}
var results = query.ToList()
(searchModel.NumberOfRooms != null
&& searchModel.NumberOfRooms.Contains(condStates.RoomsCount.ToString())
This is always going to be false when the NumberOfRooms is null (due to the null check), and given that the other bool values are all false, you will get no results.
Instead if you change to:
(searchModel.NumberOfRooms == null
|| searchModel.NumberOfRooms.Contains(condStates.RoomsCount.ToString())
You will either get everything (when the NumberOfRooms is null, or just the records that match the RoomsCount (when the NumberOfRooms is not null).
Note that in this case, if NumberOfRooms is null you will still return everything regardless of your bool filters. Which seems to be what your code requires, but I'm not sure is what you actually require, so you might want to check that.

check whether LINQ query returns rows

I need to check whether a query returns rows and if it does, change it to a string, but if it doesn't, return "In Progress". I thought the below code would work but this is always true:
if (System.Linq.Enumerable.Count(columns) == 0)<--- always true but it shouldn't be
And when there isn't a row returned to columns I get the following error in my jQuery Ajax:
"The cast to value type \u0027Int32\u0027 failed because the materialized value is null. Either the result type\u0027s generic parameter or the query must use a nullable type."
Here's my WebMethod:
using (dbPSREntities5 myEntities = new dbPSREntities5())
{
var thisId = myEntities.tbBreadCrumbs.Where(x => x.ProjectID == projectID && x.StatusID == statusID).Max(x => x.BreadCrumbID);
var columns = myEntities.tbBreadCrumbs
.Where(x => x.BreadCrumbID == thisId)
.Select(x => x.CreateDateTime)
.ToList();
if (System.Linq.Enumerable.Count(columns) == 0)
{
var formattedList = columns
.Select(d => null != d
? d.Value.ToString("MMM dd, yyyy")
: string.Empty) // this is just one example to handle null
.ToList();
return formattedList;<-- return this if there is a BreadCrumbID (columns would have a row)
}
else
{
return "In Progress";<--- there was no BreadCrumbID (columns would have 0 rows)
}
}
You could use Any()
MSDN Enumerable.Any Method
You first check for Count == 0, doesn't sound right, I guess you need the opposite check. You should use Any or Count() > 0 check, Like:
if (columns.Any()) //Or columns.Count() > 0
{
var formattedList = columns
.Select(d => null != d
? d.Value.ToString("MMM dd, yyyy")
: string.Empty) // this is just one example to handle null
.ToList();
return formattedList;<-- return this if there is a BreadCrumbID (columns would have a row)
}
else
{
return "In Progress";<--- there was no BreadCrumbID (columns would have 0 rows)
}
You have to convert your List to string, in order for your method to return a string.
Your condition is wrong. Count must be greater than zero
Use the .Any() method provided by List<T>:
If (columns.Any())
{
// do your bidding
} else {
// in progress code
}
Also your method has two different return signatures. That won't compile. You can't return a List or a string, unless the return type is object (not recommended).
I suggest to return a null list and check if it's null in your UI layer and then display the appropriate string or date values, since they end up as strings in the UI anyway.
You only needs to ckeck .Count() > 0
if (columns.Count() > 0)
{
var formattedList = columns
.Select(d => null != d
? d.Value.ToString("MMM dd, yyyy")
: string.Empty) // this is just one example to handle null
.ToList();
return formattedList;<-- return this if there is a BreadCrumbID (columns would have a row)
}
else
{
return "In Progress";<--- there was no BreadCrumbID (columns would have 0 rows)
}

Matching on search attributes selected by customer on front end

I have a method in a class that allows me to return results based on a certain set of Customer specified criteria. The method matches what the Customer specifies on the front end with each item in a collection that comes from the database. In cases where the customer does not specify any of the attributes, the ID of the attibute is passed into the method being equal to 0 (The database has an identity on all tables that is seeded at 1 and is incremental). In this case that attribute should be ignored, for example if the Customer does not specify the Location then customerSearchCriteria.LocationID = 0 coming into the method. The matching would then match on the other attributes and return all Locations matching the other attibutes, example below:
public IEnumerable<Pet> FindPetsMatchingCustomerCriteria(CustomerPetSearchCriteria customerSearchCriteria)
{
if(customerSearchCriteria.LocationID == 0)
{
return repository.GetAllPetsLinkedCriteria()
.Where(x => x.TypeID == customerSearchCriteria.TypeID &&
x.FeedingMethodID == customerSearchCriteria.FeedingMethodID &&
x.FlyAblityID == customerSearchCriteria.FlyAblityID )
.Select(y => y.Pet);
}
}
The code for when all criteria is specified is shown below:
private PetsRepository repository = new PetsRepository();
public IEnumerable<Pet> FindPetsMatchingCustomerCriteria(CustomerPetSearchCriteria customerSearchCriteria)
{
return repository.GetAllPetsLinkedCriteria()
.Where(x => x.TypeID == customerSearchCriteria.TypeID &&
x.FeedingMethodID == customerSearchCriteria.FeedingMethodID &&
x.FlyAblityID == customerSearchCriteria.FlyAblityID &&
x.LocationID == customerSearchCriteria.LocationID )
.Select(y => y.Pet);
}
I want to avoid having a whole set of if and else statements to cater for each time the Customer does not explicitly select an attribute of the results they are looking for. What is the most succint and efficient way in which I could achieve this?
Criteria that are not selected are always zero, right? So how about taking rows where the field equals the criteria OR the criteria equals zero.
This should work
private PetsRepository repository = new PetsRepository();
public IEnumerable<Pet> FindPetsMatchingCustomerCriteria(CustomerPetSearchCriteria customerSearchCriteria)
{
return repository.GetAllPetsLinkedCriteria()
.Where(x => (customerSearchCriteria.TypeID == 0 || x.TypeID == customerSearchCriteria.TypeID)&&
(customerSearchCriteria.FeedingMethodID == 0 || x.FeedingMethodID == customerSearchCriteria.FeedingMethodID) &&
(customerSearchCriteria.FlyAblityID == 0 || x.FlyAblityID == customerSearchCriteria.FlyAblityID) &&
(customerSearchCriteria.LocationID == 0 || x.LocationID == customerSearchCriteria.LocationID))
.Select(y => y.Pet);
}
Alternatively, if this is something you find yourself doing alot of, you could write an alternate Where extension method that either applies the criteria or passes through if zero, and chain the calls instead of having one condition with the criteria anded. Then you'd do the comparision for the criteria == 0 just once per query, not for every unmatched row. I'm not sure that it's worth the - possible - marginal performance increase, you'd be better off applying the filters in the database if you want a performance gain.
Here it is anyway, for the purposes of edification . . .
static class Extns
{
public static IEnumerable<T> WhereZeroOr<T>(this IEnumerable<T> items, Func<T, int> idAccessor, int id)
{
if (id == 0)
return items;
else
return items.Where(x => idAccessor(x) == id);
}
}
private PetsRepository repository = new PetsRepository();
public IEnumerable<Pet> FindPetsMatchingCustomerCriteria(CustomerPetSearchCriteria customerSearchCriteria)
{
return repository.GetAllPetsLinkedCriteria()
.WhereZeroOr(x => x.TypeID, customerSearchCriteria.TypeID)
.WhereZeroOr(x => x.FeedingMethodID, customerSearchCriteria.FeedingMethodID)
.WhereZeroOr(x => x.FlyAblityID, customerSearchCriteria.FlyAblityID)
.WhereZeroOr(x => x.LocationID, customerSearchCriteria.LocationID);
}
Looks like you're using a stored procedure and you're getting all records first and then doing your filtration. I suggest you filter at the stored procedure level, letting the database do the heavy lifting and any micro filtration that you need to do afterwords will be easier. In your sproc, have your params default to NULL and make your properties nullable for the criteria object so you can just pass in values and the sproc will(should) be corrected to work with these null values, i.e.
private PetsRepository repository = new PetsRepository();
public IEnumerable<Pet> FindPetsMatchingCustomerCriteria(CustomerPetSearchCriteria customerSearchCriteria)
{
return repository.GetAllPetsLinkedCriteria(customerSearchCriteria.TypeID,customerSearchCriteria.FeedingMethodID,customerSearchCriteria.FlyAblityID,customerSearchCriteria.LocationID).ToList();
}
I'm not seeing an elegant solution. May be this:
IEnumerable<Pet> FindPetsMatchingCustomerCriteria(CustomerPetSearchCriteria customerSearchCriteria)
{
return repository.GetAllPetsLinkedCriteria()
.Where(x =>
Check(x.TypeID, customerSearchCriteria.TypeID) &&
Check(x.FeedingMethodID, customerSearchCriteria.FeedingMethodID) &&
Check(x.FlyAblityID, customerSearchCriteria.FlyAblityID) &&
Check(x.LocationID, customerSearchCriteria.LocationID))
.Select(x => x.Pet);
}
static bool Check(int petProperty, int searchCriteriaProperty)
{
return searchCriteriaProperty == 0 || petProperty == searchCriteriaProperty;
}

c# Object reference not set to an instance of an object

I'm trying to work out if something does not exist in the table. I'm telling it to see if the UserInfo contains information for user.
UserInfo Character = db.UserInfoes.SingleOrDefault(a => a.Username == user);
if (Character.Username == null || Character.Username.Length == 0)
{
//do stuff
}
But I get an error on the if statement.
Object reference not set to an instance of an object.
It seems that the db.UserInfoes.SingleOrDefault(a => a.Username == user) expression returned null because it didn't find any matching records that satisfy the filter criteria.
so:
UserInfo Character = db.UserInfoes.SingleOrDefault(a => a.Username == user);
if (Character == null || Character.Username == null || Character.Username.Length == 0)
{
//do stuff
}
You wrote in the comments that you know that db.UserInfoes.SingleOrDefault(a => a.Username == user) returned null. Hence, Character is null and you need to check this case separately:
if (Character == null || // this line is new
Character.Username == null ||
Character.Username.Length == 0)
{
//do stuff
}
Since you say the error occurs on the if statement, Character is null. You need to add a check for if (Character == null).
First you should check whether Character is null, then later you should check for remaining in Character.
if(Character != null)
{
if(Character.Username == null || Character.Username.Lenght == 0)
{
//Do Stuff
}
}
EDIT:
or simply you can check only the Character, like
if(Character == null)
{
//Do Stuff
}
If you are getting an error on the if statement, then it is likely that your search:-
UserInfo Character = db.UserInfoes.SingleOrDefault(a => a.Username == user);
Has not found any record matching where Username equals user. When that happens, the value of Character is null.
Your issue is that you are trying to call a property on something that doesn't exist. You need to perform a check to ensure that Character is not null before calling any of its members.
if ( Character != null )
{
// Can now safely call properties on the Character object
}
else
{
// Take the appropriate action for circumstances where we can't
// find a user by username
}
You need to add a test on Character to know if it's null. If that's the case, you'll have the exception you mentionned.
So just do this :
if(Character != null)
{
//Your code can now safely call the Properties/Methods/etcetc...
}

FindAll in a c# List, but varying search terms

List<DTOeduevent> newList = new List<DTOeduevent>();
foreach (DTOeduevent e in eduList.FindAll(s =>
s.EventClassID.Equals(cla)
&& s.LocationID.Equals(loc)
&& s.EducatorID.Equals(edu)))
newList.Add(e);
cla, loc, edu can be (null or empty) or supplied with values--
basically how can I simply return the original list (eduList) if cla, loc, edu are all null
or search by loc, search by loc, edu, search by edu, cla -- etc........
my sample code only makes a new list if all 3 vars have values--
is there an elegant way to do this, without brute force if statements?
List<DTOeduevent> newList = eduList.FindAll(s =>
(cla == null || s.EventClassID.Equals(cla))
&& (loc == null || s.LocationID.Equals(loc))
&& (edu == null || s.EducatorID.Equals(edu)));
Assuming the values are Nullable value types or classes. If they're strings, you could replace cla == null with String.IsNullOrEmpty(cla).
IEnumerable<DTOeduevent> newList = eduList;
if (cla != null)
{
newList = newList.Where(s => s.EventClassID == cla);
}
if (loc != null)
{
newList = newList.Where(s => s.LocationID == loc);
}
if (edu != null)
{
newList = newList.Where(s => s.EducatorID == edu);
}
newList = newList.ToList();
Due to deferred execution, the Where statements should all execute at once, when you call ToList; it will only do one loop through the original list.
I would personally lean towards something that encapsulated the logic of what you seem to be doing here: checking that a found id is equal to some search id. The only wrinkle is how to get that check for null or empty in there first.
One way to do that is by using a static extension method:
public static class DtoFilterExtensions
{
public static bool IsIdEqual(this string searchId, string foundId) {
Debug.Assert(!string.IsNullOrEmpty(foundId));
return !string.IsNullOrEmpty(searchId) && foundId.Equals(searchId);
}
}
I would also lean towards using LINQ and IEnumerable<> as Domenic does, even though you could make it work with List.FindAll just as easily. Here would be a sample usage:
public void Filter(string cla, string loc, string edu) {
var startList = new List<DTOeduevent>();
var filteredList = startList
.Where(x => x.classId.IsIdEqual(cla) && x.locationId.IsIdEqual(loc) && x.educatorId.IsIdEqual(edu));
Show(filteredList.ToList());
}
In your own code of course you have got that start list either in a member variable or a parameter, and this assumes you have got some method like Show() where you want to do something with the filtered results. You trigger the deferred execution then, as Domenic explained with the ToList call (which is of course another extension method provided as part of LINQ).
HTH,
Berryl

Categories

Resources