Matching on search attributes selected by customer on front end - c#

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

Related

Entity Framework error using DefaultIfEmpty()

I have a problem with Entity Framework using the DefaultIfEmpty method. The following query is returning empty when it should return an offer that matches all criteria in the database.
If I remove one or both DefaultIfEmpty method calls it works, but with them it doesn't. I need those to prevend another problem in the query.
When I execute the generated SQL query directly on the database it works and it returns the offer.
I also made an Unit Test reproducing the same example and it also passes so it must be an Entity Framework issue.
Here's the query:
private static Expression<Func<Offer, bool>> AddFilter(Service criteria)
{
return offer => offer.Restrictions.
SelectMany(rest => rest.OperatorRange.DefaultIfEmpty(), (rest, alop) => new { Restriction = rest, OperatorRange = alop.Id }).
Where(alop => criteria.ServiceUseNet == null || alop.OperatorRange.ToUpper() == criteria.ServiceUseNet.ToUpper()).
SelectMany(rest => rest.Restriction.CallType.DefaultIfEmpty(), (rest, till) => new { Restriction = rest, CallType = till.Id }).
Any(till => criteria.UseServiceCoverage == null || till.CallType.ToUpper() == criteria.UseServiceCoverage.ToUpper());
}
Change it into two Any calls:
return offer => offer.Restrictions
.Any(rest
=> rest.OperatorRange
.Where(alop => criteria.ServiceUseNet == null
|| alop.OperatorRange.ToUpper() == criteria.ServiceUseNet.ToUpper())
.Any(till => criteria.UseServiceCoverage == null
|| till.CallType.ToUpper() == criteria.UseServiceCoverage.ToUpper()));
The predicate is supposed to test whether there are any OperatorRanges (meeting some criteria) having any CallTypes meeting some criteria. If there are no OperatorRanges, there won't be any CallTypes either, let alone matching CallTypes.
In this form, the predicate always returns true or false.

Search based off a changing list of parameters

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());

Dynamic where condition in LINQ

I have a scenario where I have to use a dynamic where condition in LINQ.
I want something like this:
public void test(bool flag)
{
from e in employee
where e.Field<string>("EmployeeName") == "Jhom"
If (flag == true)
{
e.Field<string>("EmployeeDepartment") == "IT"
}
select e.Field<string>("EmployeeID")
}
I know we can't use the 'If' in the middle of the Linq query but what is the solution for this?
Please help...
Please check out the full blog post: Dynamic query with Linq
There are two options you can use:
Dynamic LINQ library
string condition = string.Empty;
if (!string.IsNullOrEmpty(txtName.Text))
condition = string.Format("Name.StartsWith(\"{0}\")", txtName.Text);
EmployeeDataContext edb = new EmployeeDataContext();
if(condition != string.empty)
{
var emp = edb.Employees.Where(condition);
///do the task you wnat
}
else
{
//do the task you want
}
Predicate Builder
Predicate builder works similar to Dynamic LINQ library but it is type safe:
var predicate = PredicateBuilder.True<Employee>();
if(!string.IsNullOrEmpty(txtAddress.Text))
predicate = predicate.And(e1 => e1.Address.Contains(txtAddress.Text));
EmployeeDataContext edb= new EmployeeDataContext();
var emp = edb.Employees.Where(predicate);
difference between above library:
PredicateBuilder allows to build typesafe dynamic queries.
Dynamic LINQ library allows to build queries with dynamic Where and OrderBy clauses specified using strings.
So, if flag is false you need all Jhoms, and if flag is true you need only the Jhoms in the IT department
This condition
!flag || (e.Field<string>("EmployeeDepartment") == "IT"
satisfies that criterion (it's always true if flag is false, etc..), so the query will become:
from e in employee
where e.Field<string>("EmployeeName") == "Jhom"
&& (!flag || (e.Field<string>("EmployeeDepartment") == "IT")
select e.Field<string>("EmployeeID")
also, this e.Field<string>("EmployeeID") business, smells like softcoding, might take a look into that. I guess
from e in employee
where e.EmployeeName == "Jhom"
&& (!flag || (e.EmployeeDepartment == "IT")
select e.EmployeeID
would be more compact and less prone to typing errors.
EDIT: This answer works for this particular scenario. If you have lots of this kinds of queries, by all means investingate the patterns proposed in the other answers.
You can chain methods :
public void test(bool flag)
{
var res = employee.Where( x => x.EmployeeName = "Jhom" );
if (flag)
{
res = res.Where( x => x.EmployeeDepartment == "IT")
}
var id = res.Select(x => x.EmployeeID );
}
from e in employee
where e.Field<string>("EmployeeName") == "Jhom" &&
(!flag || e.Field<string>("EmployeeDepartment") == "IT")
select e.Field<string>("EmployeeID")
You can call LINQ methods explicitly and chain them conditionally.
public IEnumerable<string> FilterEmployees (IEnumerable<Employee> source, bool restrictDepartment)
{
var query = source.Where (e => e.Field<string>("EmployeeName") == "Jhom");
if (restrictDepartment) // btw, there's no need for "== true"
query = query.Where (e => e.Field<string>("EmployeeDepartment") == "IT");
return query.Select (e => e.Field<string>("EmployeeID"));
}

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

Test for List<T> membership using a List<T>

Does anyone know if there is a way to test for list membership utilizing a list. For example I have a class named Membership which has a property Rebates which is of type List<Enums.RebateType>. I want to test using a lambda expression to see if that list contains any rebates that are of a specific type. My orginal lambda expression is as follows
return Membership.Rebates.Exists(rebate =>
rebate.RebateType == Enums.RebateType.A &&
rebate.RebateStatus == Enums.RebateStatus.Approved);
Instead of having to do the following:
return Membership.Rebates.Exists(rebate =>
(rebate.RebateType == Enums.RebateType.A &&
rebate.RebateStatus == Enums.RebateStatus.Approved) ||
(rebate.RebateType == Enums.RebateType.B &&
rebate.RebateStatus == Enums.RebateStatus.Approved));
I was wondering if something similar to the following mocked up SQL syntax could be done via one Lambda expression.
SELECT COUNT(*)
FROM Membership.Rebates
WHERE RebateType IN (ValidRebateTypes) AND Approved = true
ValidRebateTypes is curently a List<Enums.RebateType> that I am testing for i.e. ValidRebateTypes = (Enums.RebateType.A, Enums.RebateType.B).
The work around I currently have is as follows:
bool exists = false;
foreach (Enums.RebateType rebateType in ValidRebateTypes())
{
exists = Membership.Rebates.Exists(
rebate =>
rebate.RebateType == rebateType &&
rebate.RebateStatus == Enums.RebateStatus.Approved);
if (exists) { break; }
}
return exists;
Sounds like you want:
Membership.Rebates.Where(r => ValidRebateTypes.Contains(r.RebateType)
&& r.RebateStatus == Enums.RebateStatus.Approved);
You can then use .Count() for the count:
Membership.Rebates.Where(r => ValidRebateTypes.Contains(r.RebateType)
&& r.RebateStatus == Enums.RebateStatus.Approved)
.Count();
Or .Any() to determine the existence of any that satisfy that condition
Membership.Rebates.Any(r => ValidRebateTypes.Contains(r.RebateType)
&& r.RebateStatus == Enums.RebateStatus.Approved);
In addition to Marc's suggestion, I would recomment making ValidRebateTypes a HashSet<Enums.RebateType>. Not only is this likely to be more efficient (although possibly not for a small set), it also reveals your intent (ie. that there only be one of each value of RebateType in it).

Categories

Resources