Search based off a changing list of parameters - c#

I have a list of these objects:
public class seat
{
public String id, tooltip;
public String Section, Row, Number;
public Boolean Taken;
}
I would like to build a function to search the elements of the class. However, I will not always be searching for all of the elements.
I know I could do this with a loop, and some if-statements. Saying something along the lines of
public ArrayList searchArray(String section, String row, String number)
{
ArrayList searched = new ArrayList();
foreach(seat item in seats)//seats is a list of the seat class
{
if(section!="" && row!=""&& id!="")
{
if(item.Section==section && item.Row==row &&item.id==id)
searched.Add(item);
}
else if(section!="" && row!="")
{
if(item.Section==section && item.Row==row)
searched.Add(item);
}
else if(row!="")
{
if(item.Row==row)
searched.Add(item);
}
/////Continue this for all the other combinations of searching
}
return searched;
}
I could also to it several loops like
if(Section!="")
foreach(seat item in seats)
if(item.Section==section)
searched.Add(item);
seats = searched;
search.Clear();
if(id!="")
foreach(seat item in seats)
if(item.id==id)
searched.Add(item);
seats = searched;
search.Clear();
if(row!="")
foreach(seat item in seats)
if(item.Row==row)
searched.Add(item);
So first one is tedious and requires a lot of ugly code.
The second is a little better, but requires I go through the list more than once. More specifically it requires me to go through the list for each parameter I am looking for.
Is there a way I can do this where you just add the parameters you want to look for and then search. Sort of like you generate an sql query to search.
Less important, but would be amazing if it could work, would be to even allow ranges for the search. Like id>2 && id<12

This is where IEnumerable<> is your friend!
IEnumerable<seat> query = seats.AsEnumerable();
if(!string.IsNullOrEmpty(section))
query = query.Where(s => s.Section == section);
if(!string.IsNullOrEmpty(row))
query = query.Where(s => s.Row == row);
if(!string.IsNullOrEmpty(id))
query = query.Where(s => s.Id == id);
List<seat> results = query.ToList(); // deferred execution

ArrayList searched = new ArrayList(
seats.Where(c => c.Section == section && !string.IsNullOrEmpty(section))
.Where(c => c.Row == row && !string.IsNullOrEmpty(row))
.Where(c => c.id == id && !string.IsNullOrEmpty(id)).ToList());

Related

Getting a List<Items> out of List<List<Items>>

I have a object called Items. I use that object like so.
List<List<Items>> myItems = new List<List<Items>>();
I want to know how to get a specific List<Items> out of the List<List<Items>> object. Currently I am using foreach loops with some rules to find a specific List<Items>
This is currently what I am doing
foreach (List<Items> db2Items in db2Data)
{
foreach (List<Items> apiItems in apiData)
{
if (db2Items[0].Value == apiItems[0].Value && db2Items[27].Value == apiItems[27].Value)
{
/// Some other logic here
}
}
}
I was wanting to use LINQ to get the matching List<items> out of apiData and if I have a result then do the logic I wanted to do.
Not really sure what you're trying to accomplish, but if you want to get a list in a list based on a certain condition you can do it like this:
var item = db2Data.Where(x => x.Where(y => y.[value] == [YourCondition]).Any()).FirstOrDefault();
the "x.Where(y => y == [YourCondition]).Any()" will return true if the condition is met, and firstordefault will then return the list that meets the condition.
I have figured it out.
Using LINQ:
foreach (List<Items> db2Items in db2Data)
{
IEnumerable<List<Items>> theItem = from item in apiData
where item[0].Value == db2Items[0].Value && item[27]>Value == db2Items[27].Value
select item;
if (theItem.ToList().Count > 0)
{
// Do something
}
}

Iterate over an IQueryable without calling ToList()

I have a DB used for a production line. It has an Orders table, and Ordertracker table, an Item table, and an Itemtracker table.
Both Orders and Items have many-to-many relationships with status. The tracker tables resolves these relationships in such a way that an item can have multiple entries in the tracker - each with a particular status.
I tried to upload a picture of the tables to make things clearer but alas, I don't have enough points yet :C
I need to find items whose last status in the Itemtracker table meets a condition, either '3' or '0'.
I then need to get the first one of these items.
The steps I am using to accomplish this are as follows:
Get all the Orders which have a certain status.
Get all the Items in that Order.
Get all the Items whose last status was = 0 or 3.
Get the first of these items.
My code is as follows:
public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
{
var firstOrder = GetFirstOrderInProductionAndNotCompleted();
var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;
if (CheckStatusOfItems(items) != null)
{
var nextItem = CheckStatusOfItems(items);
return nextItem ;
}
else
{
return null;
}
}
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
List<ITEM> listOfItemsToProduce = new List<ITEM>();
foreach (ITEM item in items.ToList())
{
var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();
if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
{
listOfItemsToProduce.Add(item);
}
}
return listOfItemsToProduce.FirstOrDefault();
}
Now, this all works fine and returns what I need but I'm aware that this might not be the best approach. As it is now my IQueryable collection of items will never contain more than 6 items - but if it could grow larger, then calling ToList() on the IQueryable and iterating over the results in-memory would probably not be a good idea.
Is there a better way to iterate through the IQueryable items to fetch out the items that have a certain status as their latest status without calling ToList() and foreaching through the results?
Any advice would be much appreciated.
Using LINQ query syntax, you can build declaratively a single query pretty much the same way you wrote the imperative iteration. foreach translates to from, var to let and if to where:
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
let lastStatusOfItem = ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.FirstOrDefault()
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
or alternatively using from instead of let and Take(1) instead of FirstOrDefault():
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
from lastStatusOfItem in ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.Take(1)
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}

LINQ grouping multiple columns with nested collection

I am grouping a collection, by multiple columns. It is easy to group them if they are on this same level of nesting:
var groupedAirProducts = airproductsPerClient.GroupBy(ac => new
{
ac.AirProduct.PassengerNameRecord,
ac.AirProduct.Flights.First().MarketingAirlineCode,
ac.AirProduct.Flights.First().FlightNo,
ac.AirProduct.Flights.First().DepartureDate
})
The problem is that I don't want only to group by a first flight, but I want to include all the flights. What I would like to do is something like:
var groupedAirProducts= airproductsPerClient.GroupBy(ac =>
{
ac.AirProduct.PassengerNameRecord,
foreach(var flight in ac.AirProduct.Flights){
flight.MarketingAirlineCode,
flight.FlightNo,
flight.DepartureDate
}
})
Please note that the code above is just an illustration of an idea. It is not a working/proper code. The only way I know how to do it is pretty ugly and complex. I was wondering if there is a nice way to do it using LINQ, or a simple way to tackle this problem.
Edit:
To explain more of what is expected. As a result of grouping I want to have collections of airProducts that share same PassengerNameRecord, and that flights that belong to a given air product share same MarketingAirlineCode, FlightNo, DepartureDate. I know how to implement it by flattering a collection of PassengerNameRecord, so the Flights are included in it, grouping it, so I have a groups of air products that share grouped properties. And then rebuilding the flattered structure. I was hoping that there is a way to either add groupBy properties by iterating thru some collection, or there is a way to merge grouped collection - if there is a way to have a collection grouped by PassengerNameRecord, and collection grouped by Flight properties and merging them together, unfortunately I doubt that there is an easy way to do such merge.
You can implement a custom IEqualityComparer<AirClient> for GroupBy(or other LINQ methods).
You can implement it in the following way:
public class AirClientComparer : IEqualityComparer<AirClient>
{
public bool Equals(AirClient lhs, AirClient rhs)
{
if (lhs == null || rhs == null) return false;
if(object.ReferenceEquals(lhs, rhs)) return true;
if(lhs.PassengerNameRecord != rhs.PassengerNameRecord) return false;
if(object.ReferenceEquals(lhs.AirProduct, rhs.AirProduct)) return true;
if(lhs.AirProduct == null || rhs.AirProduct == null) return false;
if(object.ReferenceEquals(lhs.AirProduct.Flights , rhs.AirProduct.Flights )) return true;
if(lhs.AirProduct.Flights.Count != rhs.AirProduct.Flights.Count) return false;
if(lhs.AirProduct.Flights.Count == 0 && rhs.AirProduct.Flights.Count == 0) return true;
return lhs.AirProduct.Flights.All(f =>
rhs.AirProduct.Flights.Any(f2 =>
f.MarketingAirlineCode == f2.MarketingAirlineCode
&& f.FlightNo == f2.FlightNo
&& f.DepartureDate == f2.DepartureDate));
}
public int GetHashCode(AirClient obj)
{
if(obj.AirProduct == null) return 0;
int hash = obj.AirProduct.PassengerNameRecord == null
? 17 : 17 * obj.AirProduct.PassengerNameRecord.GetHashCode();
unchecked
{
foreach(var flight in obj.AirProduct.Flights)
{
hash = (hash * 31) + flight.MarketingAirlineCode == null ? 0 : flight.MarketingAirlineCode.GetHashCode();
hash = (hash * 31) + flight.FlightNo == null ? 0 : flight.FlightNo.GetHashCode();
hash = (hash * 31) + flight.DepartureDate.GetHashCode();
}
}
return hash;
}
}
Now you can use it for example in GroupBy:
var groupedAirProducts = airproductsPerClient.GroupBy(ac => new AirClientComparer());
In such cases, you need to start with the inner table.
The inner table also needs a reference to the outer table, such like you have a
ICollection<Flight> Flights in AirProduct
you also need a Airproduct Member inside of Flight.
Then group your flights like this (pseudo code)
AirProduct.Flights.groupby( flight => new{ flight.MarketingAirlineCode,
flight.FlightNo,
flight.DepartureDate,
flight.product.PassengerNameRecord
});

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;
}

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