Test for List<T> membership using a List<T> - c#

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).

Related

How to make reusable conditions that can be used in queryables? [duplicate]

This question already has answers here:
How to reuse where clauses in Linq To Sql queries
(4 answers)
Closed 1 year ago.
So in a function I've a large queryable and I apply a bunch of where cause on it based on other conditions.
Like in this example:
query.Where(i =>
_context.FicDernierEvt
.Where(y => y.VteAffaire == null && y.ApvAffaire == null)
.Select(y => y.IdFicheCrm)
.Contains(i.Id)
);
I've this condition _context.FicDernierEvt.Where(y => y.VteAffaire == null && y.ApvAffaire == null).Select(y => y.IdFicheCrm).Contains(i.Id) that is used a lot in my code.
I would like to avoid having this all accross my code so i've tried to make a function:
private bool isProspect(FicFicheCrm ficheCrm){
return _context.FicDernierEvt
.Where(y => y.VteAffaire == null && y.ApvAffaire == null)
.Select(y => y.IdFicheCrm)
.Contains(ficheCrm.Id);
}
So i could use it this way:
query.Where(i => isProspect(i));
But it didn't worked since, it's just not mean to be done that way.
Do someone have an idea on how to make reusable conditions like this to be used in queryables ?
My advice would be to extend LINQ with a Where method that contains your predicate.
If you are not familiar with Extension methods, consider to read Extension Methods Demystified
You fogrot to tell us what type of IQueryable<...> you store in _context.FicDernierEvt, but let's assume it is IQueryable<FicDernierEvt>. In other words, assume that _context.FicDernierEvt is a table of FicDernierEvts.
Requirement Procedure GetIdFics (TODO: invent proper name) takes as input an IQueryable<FicDernierEvt>, keeps only those ficDernierEvts that have a null value for both properties VteAffaire and ApvAffaire, and returns from every remaining ficDernierEvt the value of property ficDernierEvt.IdFic
I don't know the type of IdFic, but let's assume it is an int
public static IQueryable<int> GetIdFics( // TODO: invent proper name
this IQueryable<FicDernierEvt> source)
{
return source.Where(ficDernierEvt => ficDernierEvt.VteAffaire == null
&& ficDernierEvt.ApvAffaire == null)
.Select(ficDernierEvt => ficDernierEvt.IdFic);
}
That's all!
Usage:
IQueryable<int> myIdFics = _context.FicDernierEvt.GetIdFics();
You say you have this Where/Select in a lot of places:
var oldIdFics = _context.FicDernierEvt
.Where(ficDernierEvt.Date.Year < 2010)
.GetIdfics();
var invalidIdFics = _context.FicDernierEvt.GetIdFics()
.Where(idFic => idFic <= 0);
You can even use it in a more complicate LINQ statement:
IQueryable<FicDernierEvt> unpaidFicDernierEvts = this.GetUnpaidFicDernierEvts();
int customerId = this.GetCustomerId(customerName);
var largestUnpaidCustomerIdFic = unpaidFicDernierEvts
.Where(unpaidEvt => unpaidEvt.CustomerId == customerId)
.GetIdFics()
.Max();

LINQ statement using one of two parameters

I'm working on a LINQ statement. I have a table of cities where the records have either a countryId or a stateId. I'd like to just write the one statement and have the where clause check to see which of the two parameters is null and then select on the one that is not.
Here's what I'm working with:
public List<City> Cities(int? countryTypeId, int? stateTypeId)
{
if (countryTypeId == null && stateTypeId == null)
return null;
return _db.City
.Where(x => x.StateTypeId == stateTypeId
&& x.CountryTypeId == countryTypeId)
.OrderBy(x => x.Description)
.ToDTOs();
}
I'm pretty new to LINQ, and I know this code isn't right, just adding it for context.
If the State and Country ids are all >0 you simply can do this, no need to check for null
.Where(x => x.StateTypeId == stateTypeId.GetValueOrDefault()
&& x.CountryTypeId == countryTypeId.GetValueOrDefault())
Else you need to add the condition if those nullable inputs have value or not, as mentioned in the comment
Edit: after seeing some comments, if you are looking for list of cities based on either of the parameters, then you should be using || not && in your where condition
Where(x => (stateTypeId.HasValue && stateTypeId.Value == x.StateTypeId)
|| (countryTypeId.HasValue && countryTypeId.Value == x.CountryTypeId))
Note the order matters, this code will first check if stateTypeId has value and if it has it'll match only the cities with that stateTypeId
_db.City.Where(c => c.CountryTypeId?.Equals(countryTypeId) ?? false
| c.StateTypeId?.Equals(stateTypeId) ?? false);
Using null conditional operators - when a type Id is null use the null coalescing operator to return false and fail the match - otherwise check for equality and return matching.
Note you cannot short circuit the OR operator here!
I'm not sure if this is the case, but if one of the input parameters was always null and the entries were guaranteed to always have one property null, the following would be a cool solution:
_db.City.Where(c => (c.CountryTypeId ?? c.StateTypeId) == (countryTypeId ?? stateTypeId))
My DBA has sufficiently beaten it into my head that ignoring parameters in a query (ex: WHERE Field = #PARAM or #PARAM IS NULL) can result in very bad things. As a result, I would encourage you to conditionally add only the parameters that you absolutely need. Fortunately, given that you are working with just two possible parameters, this is trivial.
Start with the base of your query, and then add to it.
var queryBase = _db.City.OrderBy(x => x.Description);
if (countryTypeId.HasValue)
{
queryBase = queryBase.Where(x => x.CountryTypeId == countryTypeId);
}
if (stateTypeId.HasValue)
{
queryBase = queryBase.Where(x => x.StateTypeId == stateTypeId);
}
return queryBase.ToDTOs(); // or .ToList() for a more universal outcome
Add whatever logic you may need if parameters are mutually exclusive, one supercedes the other, etc.

Make and Expression available to be used within a Linq to EntityFramework Expression

I have the following LINQ Expression that I need to be able to use within a LINQ to Entities query.
There is currently a property like this:
[NotMapped]
public Clinic CurrentClinic
{
get { return AdmissionRoot.CurrentFacility.Compile()(this); }
}
Which called the following:
public static Expression<Func<Admission, Clinic>> CurrentFacility =
a => a.Person.PersonLocations.Any(p => p.Clinic.FacilityType != (int)Clinic.FacilityTypes.CommunityServices)
? a.Person.PersonLocations.Where(p => p.Clinic.FacilityType != (int)Clinic.FacilityTypes.CommunityServices)
.OrderByDescending(l => l.TransferDate)
.ThenByDescending(l => l.LocationId)
.FirstOrDefault().Clinic
: a.Clinic;
The issue is that I cannot use CurrentClinic within a LINQ to Entities statement because it will give the following error:
"The specified type member 'CurrentClinic' is not supported in LINQ to
Entities"
I am new to LINQ to Entities expressions and was hoping someone would be able to refactor this so it can be used within a LINQ to Entities statement something like this:
Db.Admissions.Where(
a =>
(a.CurrentClinic != null
&& !a.CurrentClinic.Company.IsHomeCompany
&& a.ReferralClinicId == clinicid
)
||
(a.CurrentClinic.Company.IsHomeCompany
&& a.CurrentClinic.ClinicId == clinicid
)
);
If it can't be done in an Expression that I can "add" other criteria onto the end then is there another way/suggestion that would work without compromising speed too much?
The issue here is because you're using CurrentClinic in your query which is not something that Entity framework can turn into an expression. A possible alternate way to run your query could be
Db.Admissions
.Select(a => new
{
CurrentClinic = CurrentFacility(a),
Admission = a
})
.Where(a =>
(a.CurrentClinic != null && !a.CurrentClinic.Company.IsHomeCompany && a.Admission.ReferralClinicId == clinicid) ||
(a.CurrentClinic.Company.IsHomeCompany && a.CurrentClinic.ClinicId == clinicid));
Note: this is untested but should still work.

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.

LINQ and C# - Dealing with a potentially null parameter

I am relatively new to LINQ but looking for some "best practice" advice on how to deal with the following. I know there are many ways to deal with this, but looking to see how more experienced people would write the code.
My LINQ at present:
var company = (from c in db.RPTINQUIRies
where c.CONCOM == concom && c.LOPER == engineer
orderby c.CREATION_DATE descending
select c);
Now the ActionResult parameter that is being passed in here (engineer) may or may not be empty. Where it is empty, I essentially want to remove the && C.LOPER == engineer clause all together.
What's the best way to deal with this?
It sounds like you just want:
where c.CONCOM == concom && (engineer == null || c.LOPER == engineer)
Alternatively, you could build up the query step by step:
var query = db.RPTINQUIRies.Where(c => c.CONCOM == concom);
if (engineer != null)
{
query = query.Where(c => c.LOPER == engineer);
}
query = query.OrderByDescending(c => c.CREATION_DATE);

Categories

Resources