IQueryable Adding OR Conditions to the chain of queries - c#

How can i Chain EF queries with OR condition
Right now i am chaining or building the query like below and this is ended up adding AND conditions
if (model.EMailChoices?.Count() > 0)
{
query = query.Where(
c => model.EMailChoices.Contains(c.Contact.CommunicationPreferences.TPEMail)
);
}
if (model.MailChoices?.Count() > 0)
{
query = query.Where(
c => model.MailChoices.Contains(c.Contact.CommunicationPreferences.TPMail)
);
}
if (model.PhoneChoices?.Count() > 0)
{
query = query.Where(
c => model.PhoneChoices.Contains(c.Contact.CommunicationPreferences.TPTelephone)
);
}
How can we add OR conditions to this chain

bool anyEmails = model.EMailChoices?.Any() == true;
bool anyMails = model.MailChoices?.Any() == true;
bool anyPhones = model.PhoneChoices?.Any() == true;
if(anyEmails || anyMails || anyPhones)
{
query = query.Where(
c => (anyEmails && model.EMailChoices.Contains(c.Contact.CommunicationPreferences.TPEMail))
|| (anyMails && model.MailChoices.Contains(c.Contact.CommunicationPreferences.TPEMail))
|| (anyPhones && model.PhoneChoices.Contains(c.Contact.CommunicationPreferences.TPTelephone)));
}

Related

Refactor and reduce cyclomatic complexity with LINQ

I have a method that I feel like could be refactored more efficiently with LINQ.
The purpose of the function is to use some logic to determine which phone number to return. The logic is: Any returned number must be sms_capable. If a number was last used for an rx, use it, otherwise return the first valid number by type in this order: Other, Home, Office
string GetDefaultSMSPhoneNumber(IEnumerable<PhoneNumbers> patientNumbers)
{
const int PHONE_TYPE_HOME = 1;
const int PHONE_TYPE_OFFICE = 3;
const int PHONE_TYPE_OTHER = 9;
var phoneNumberByType = patientNumbers.Where(p => p.sms_capable == 1).GroupBy(p => p.phone_type_id);
// Select the phone number last used in creating a prescription
if (patientNumbers.Where(p => p.sms_capable == 1 && p.last_used_for_rx == 1).Count() > 0)
{
return patientNumbers.Where(p => p.sms_capable == 1 && p.last_used_for_rx == 1).FirstOrDefault().phone_number;
}
// If no number has been used, select a configured SMS number in the following order (Other, Home, Office)
if (patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OTHER).Count() > 0)
{
return patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OTHER).FirstOrDefault().phone_number;
}
// If no number has been used, select a configured SMS number in the following order (Other, Home, Office)
if (patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_HOME).Count() > 0)
{
return patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_HOME).FirstOrDefault().phone_number;
}
// If no number has been used, select a configured SMS number in the following order (Other, Home, Office)
if (patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OFFICE).Count() > 0)
{
return patientNumbers.Where(p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OFFICE).FirstOrDefault().phone_number;
}
return string.Empty;
}
I know the first thing I can do is filter the list to only sms_capable numbers. I feel like I should be able to use .GroupBy to group the numbers by there type, but after they're grouped I'm not sure how to return the first non empty value? I feel like I'm looking for a way to coalesce in linq?
string GetDefaultSMSPhoneNumber(IEnumerable<PhoneNumbers> patientNumbers)
{
const int PHONE_TYPE_HOME = 1;
const int PHONE_TYPE_OFFICE = 3;
const int PHONE_TYPE_OTHER = 9;
var phoneNumberByType = patientNumbers.Where(p => p.sms_capable == 1).GroupBy(p => p.phone_type_id);
var phoneNumber = patientNumbers.FirstOrDefault(p => p.sms_capable == 1 && p.last_used_for_rx == 1)?.phone_number;
// Doesn't work
if (string.IsNullOrEmpty(phoneNumber))
{
var number = phoneNumberByType.FirstOrDefault(p => p.Key == PHONE_TYPE_OTHER && p.Where(x => !string.IsNullOrEmpty(x.phone_number)) ||
(p.Key == PHONE_TYPE_HOME && p.Where(x => !string.IsNullOrEmpty(x.phone_number)) ||
(p.Key == PHONE_TYPE_OFFICE && p.Where(x => !string.IsNullOrEmpty(x.phone_number))));
}
If you need matching against predicates in specific order you can create a collection of Func<PhoneNumbers, bool> and iterate it (also if PhoneNumbers is a class or record then you don't need Count, if it is not, better use Any instead of count):
string GetDefaultSMSPhoneNumber(IEnumerable<PhoneNumbers> patientNumbers)
{
const int PHONE_TYPE_HOME = 1;
const int PHONE_TYPE_OFFICE = 3;
const int PHONE_TYPE_OTHER = 9;
var predicates = new List<Func<PhoneNumbers, bool>>()
{
p => p.sms_capable == 1 && p.last_used_for_rx == 1,
p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OTHER,
p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_HOME,
p => p.sms_capable == 1 && p.phone_type_id == PHONE_TYPE_OFFICE
}; // Can be moved to static field
// prevent potential multiple materialization of the source
var enumerated = patientNumbers as ICollection<PhoneNumbers> ?? patientNumbers.ToArray();
foreach (var predicate in predicates)
{
var firstOrDefault = enumerated.FirstOrDefault(predicate);
if (firstOrDefault is not null)
{
return firstOrDefault.phone_number;
}
}
return string.Empty;
}
Also in this particular case you can "prefilter" the enumerated with .Where(p => p.sms_capable == 1) to improve performance a bit:
// ...
var enumerated = patientNumbers
.Where(p => p.sms_capable == 1)
.ToArray();
var predicates = new List<Func<PhoneNumbers, bool>>()
{
p => p.last_used_for_rx == 1,
p => p.phone_type_id == PHONE_TYPE_OTHER,
p => p.phone_type_id == PHONE_TYPE_HOME,
p => p.phone_type_id == PHONE_TYPE_OFFICE
};
// ...
This isnt using linq, but you can refactor this by putting some of the complexity into their own methods
private IEnumerable<IGrouping<int, PhoneNumbers>> GetSmsCapablePhoneNumbersByType(IEnumerable<PhoneNumbers> patientNumbers)
{
return patientNumbers.Where(p => p.sms_capable == 1).GroupBy(p => p.phone_type_id);
}
private PhoneNumbers GetLastUsedSmsNumber(IEnumerable<PhoneNumbers> patientNumbers)
{
return patientNumbers.FirstOrDefault(p => p.sms_capable == 1 && p.last_used_for_rx == 1);
}
private PhoneNumbers GetFirstSmsNumberByType(IEnumerable<PhoneNumbers> patientNumbers, int phoneTypeId)
{
return patientNumbers.FirstOrDefault(p => p.sms_capable == 1 && p.phone_type_id == phoneTypeId);
}
public string GetDefaultSMSPhoneNumber(IEnumerable<PhoneNumbers> patientNumbers)
{
var phoneNumberByType = GetSmsCapablePhoneNumbersByType(patientNumbers);
var lastUsedSmsNumber = GetLastUsedSmsNumber(patientNumbers);
if (lastUsedSmsNumber != null)
{
return lastUsedSmsNumber.phone_number;
}
var defaultSmsNumber = GetFirstSmsNumberByType(patientNumbers, PHONE_TYPE_OTHER)
?? GetFirstSmsNumberByType(patientNumbers, PHONE_TYPE_HOME)
?? GetFirstSmsNumberByType(patientNumbers, PHONE_TYPE_OFFICE);
if (defaultSmsNumber != null)
{
return defaultSmsNumber.phone_number;
}
return string.Empty;
}
If you do it correctly, your method names should describe exactly whats happening, so when somone else reads your code they should be able to follow whats happening by reading the method names (This also means there is less need for comments)

Entity Framework, LINQ Query

How to loop through the Items below and update the Field which have empty values with the value (!)
Error:
Cannot implicitly convert type System.threading.task to
System.Collections.Generic.IEnumerable<Respositories.AssignmentMasterData>
using (SUPEntities db = new SUPEntities())
{
IEnumerable<AssignementMasterData> masterDatas = null;
masterDatas = db.AssignementMasterDatas
.Where(m => DbFunctions.TruncateTime(m.CreatedDateTime) >= DbFunctions.TruncateTime(criteria.FilterStartDate)
&& DbFunctions.TruncateTime(m.CreatedDateTime) <= DbFunctions.TruncateTime(criteria.FilterEndDate)
&& (m.AssignmentNoteNumber == criteria.AssigmentNumber || criteria.AssignmentNumber == null)
&& (m.BaseCourseId == criteria.courseId || criteria.CourseId == 0)
&& (m.AccountNumber == criteria.AccountNumber || criteria.AccountNumber == null)
&& (m.ReferenceNumber == criteria.ReferenceNumber || criteria.ReferenceNumber == null)
&& (m.FacultyCode == criteria.FAcultyCode || criteria.FacultyCode == null)
&& (m.Processed == criteria.Processed)
&& (m.ClassNumber == criteria.ClassNumber || criteria.ClassNumber == null))
.ForEachAsync(t => t.AssignmentNoteIdentifiedClasses.Select(e => String.IsNullOrEmpty(e.Category)? "(!)": e.Category));
}
Use .Include( m => m.AssignmentNoteIdentifiedClasses ) to bring-in related data in a single query, this is much faster than loading each set of related AssignmentNoteIdentifiedClasses in your for-each-row loop.
This is also known as the "N+1 Problem" in ORMs.
You don't need to use TruncateTime.
In fact, you shouldn't because that will mean your query isn't SARGable.
Avoid functions in SQL predicates.
Instead, just round criteria.FilterStartDate down to the start-of-day in application code and compare it normally with m => m.CreatedDateTime >= filterStart.
Similarly, FilterEndDate should be rounded-up and then compared like so: m => m.CreatedDateTime < filterEnd
Always use exclusive upper-bounds. It makes everything, especially date-range predicates, much easier to deal with.
You don't need inline && in your Where. Use additional separate .Where() clauses instead. They'll be added to the same (single) WHERE clause as separate AND terms.
I assume EF Core isn't sophisticated enough to recognize the "NULL-means-ignore" anti-pattern for optional search predicates, in which case DON'T USE the "NULL-means-ignore" anti-pattern IN AN IQUERYABLE<T> PREDICATE!
This is bad for so many reasons: namely because query execution-plans are based on the structure ("shape") of the SQL query and not on parameter values, so the same cached execution-plan for non-NULL parameters will be used when some, or even all parameters are NULL - which is a problem. Also, be sure to read up on parameter sniffing.
Instead build your query by using IQueryable<T>'s Linq extensions and reassigning to itself.
e.g. IQueryable<T> query = db.Etc; query = query.Where( e => etc );
Each .Where() is added as an AND condition. If you want to build-up an OR condition then use PredicateBuilder.
DateTime filterStart = criteria.FilterStartDate.Date;
DateTime filterEndExcl = criteria.FilterEndDate .Date.AddDays(1);
using (SUPEntities db = new SUPEntities())
{
IQueryable<AssignementMasterData> query = db.AssignementMasterDatas
.Include( m => m.AssignmentNoteIdentifiedClasses )
.Where( m => m.CreatedDateTime >= filterStart )
.Where( m => m.CreatedDateTime < filterEndExcl ) // Exclusive upper-bound.
.Where( m => m.Processed == criteria.Processed )
.Where( m => m.ClassNumber == criteria.ClassNumber )
;
if( criteria.AssigmentNumber != null )
{
query = query.Where( m => m.AssignmentNoteNumber == criteria.AssigmentNumber );
}
if( criteria.AccountNumber != null )
{
query = query.Where( m => m.AccountNumber == criteria.AccountNumber );
}
if( criteria.CourseId != null && criteria.CourseId.Value > 0 )
{
query = query.Where( m => m.BaseCourseId == criteria.CourseId );
}
if( criteria.ReferenceNumber != null )
{
query = query.Where( m => m.ReferenceNumber == criteria.ReferenceNumber );
}
if( criteria.FacultyCode != null )
{
query = query.Where( m => m.FacultyCode == criteria.FacultyCode );
}
if( criteria.ClassNumber != null )
{
query = query.Where( m => m.ClassNumber == criteria.ClassNumber );
}
List<AssignementMasterData> rows = await query.ToListAsync().ConfigureAwait(false);
List<String> categories = rows
.SelectMany( r => r.AssignmentNoteIdentifiedClasses )
.Select( String.IsNullOrEmpty(e.Category)? "(!)": e.Category) )
.ToList();
return categories;
}
The above can be simplified by adding a new extension-method (make sure you use Expression<Func<...>> and not just Func<> so that EF can still interpret the query:
public static class MyQueryableExtensions
{
public static IQueryable<T> WhereIfNotNull<T,TValue>( this IQueryable<T> query, TValue? value, Expression<Func<T,Boolean>> predicate )
where TValue : struct
{
if( value.HasValue && value.Value != default(TValue) )
{
return query.Where( predicate );
}
else
{
return query;
}
}
}
Used like so:
// `criteria` is now named `c` for brevity.
DateTime filterStart = c.FilterStartDate.Date;
DateTime filterEndExcl = c.FilterEndDate .Date.AddDays(1);
using (SUPEntities db = new SUPEntities())
{
IQueryable<AssignementMasterData> query = db.AssignementMasterDatas
.Include( m => m.AssignmentNoteIdentifiedClasses )
.Where( m => m.CreatedDateTime >= filterStart )
.Where( m => m.CreatedDateTime < filterEndExcl ) // Exclusive upper-bound.
.Where( m => m.Processed == c.Processed )
.Where( m => m.ClassNumber == c.ClassNumber )
.WhereIfNotNull( c.AssigmentNumber, m => m.AssignmentNoteNumber == c.AssigmentNumber )
.WhereIfNotNull( c.AccountNumber , m => m.AccountNumber == c.AccountNumber )
.WhereIfNotNull( c.CourseId , m => m.BaseCourseId ​ == c.CourseId )
​.WhereIfNotNull( c.ReferenceNumber, m => m.ReferenceNumberr == c.ReferenceNumber )
​.WhereIfNotNull( c.FacultyCode , m => m.FacultyCoder == c.FacultyCode )
​.WhereIfNotNull( c.ClassNumber , m => m.ClassNumber == c.ClassNumber )
;
List<AssignementMasterData> rows = await query.ToListAsync().ConfigureAwait(false);
List<String> categories = rows
.SelectMany( r => r.AssignmentNoteIdentifiedClasses )
.Select( String.IsNullOrEmpty(e.Category)? "(!)": e.Category) )
.ToList();
return categories;
}
Firstly, about the error message:
You are trying to assign the wrong type to your masterDatas variable.
You declare it as a IEnumerable<Respositories.AssignmentMasterData>, but the ForEachAsync at the last line will return a Task, hence the error message.
See the ForEachAsync signature:
public static System.Threading.Tasks.Task ForEachAsync (this
System.Linq.IQueryable source, Action action);
Secondly. You want to return an IEnumerable<Respositories.AssignmentMasterData>
If you can content with a synchronous method, you could do it this way:
You will need to some point transform your IQueryable into an IEnumerable. A call to AsEnumerable() does that. Then you need to replace some values. So you need to project your collection using a Select.
using (SUPEntities db = new SUPEntities())
{
var masterDatas = db.AssignementMasterDatas
.Where(m => DbFunctions.TruncateTime(m.CreatedDateTime) >= DbFunctions.TruncateTime(criteria.FilterStartDate)
&& DbFunctions.TruncateTime(m.CreatedDateTime) <= DbFunctions.TruncateTime(criteria.FilterEndDate)
&& (m.AssignmentNoteNumber == criteria.AssigmentNumber || criteria.AssignmentNumber == null)
&& (m.BaseCourseId == criteria.courseId || criteria.CourseId == 0)
&& (m.AccountNumber == criteria.AccountNumber || criteria.AccountNumber == null)
&& (m.ReferenceNumber == criteria.ReferenceNumber || criteria.ReferenceNumber == null)
&& (m.FacultyCode == criteria.FAcultyCode || criteria.FacultyCode == null)
&& (m.Processed == criteria.Processed)
&& (m.ClassNumber == criteria.ClassNumber || criteria.ClassNumber == null))
.AsEnumerable()
.Select(a =>
{
a.AssignmentNoteIdentifiedClasses = a.AssignmentNoteIdentifiedClasses
.Select(e =>
{
e.Category = string.IsNullOrWhiteSpace(e.Category) ? "(!)" : e.Category;
return e;
})
.ToList(); // Depending on the type of AssignmentNoteIdentifiedClasses, ToList() might be replaced.
return a;
});
return masterDatas;
}

How to write a linq query to exclude some of the records?

This is my LINQ
IList<string> ExceptList = new List<string>() { "045C388E96", "C9B735E166", "02860EB192", "2401016471" };
var listusers = context.USER_INFO.Where(x => x.ACTIVATED
&& x.COMP.EQUIPMENT.Count(y => y.STATUS == (int)STATUSEQ.ACTIVE) > 0
&& (x.LAST_LOGIN < time)
&& !ExceptList.Contains(x.COMP.CODE)
&& !x.IS_LOCK
|| !x.COMP.IS_LOCK)
.Select(x => new EmailOutOfDateLoginModel
{
COMPCode = x.COMP.CODE,
First_Name = x.FIRST_NAME,
Last_Name = x.LAST_NAME,
Total_EQ = x.COMP.EQUIPMENT.Count(y => y.STATUS == (int)STATUSEQ.ACTIVE),
User_Email = x.USER_EMAIL
}).ToList();
I am not sure why my ExceptList is not working. I want to exclude any record that contaisn any of the CODE in the ExceptList
Put parentheses around the expressions containing the && logic. The || at the end is only matched with the !x.IS_LOCK || !x.COMP.IS_LOCK otherwise.
According your linq all records where (!x.COMP.IS_LOCK==true) will be included in the query. Try this "where" part:
.Where(x => x.ACTIVATED
&& x.COMP.EQUIPMENT.Count(y => y.STATUS == (int)STATUSEQ.ACTIVE) > 0
&& (x.LAST_LOGIN < time)
&& !ExceptList.Contains(x.COMP.CODE)
&& !(x.IS_LOCK && x.COMP.IS_LOCK))

Adding additional linq where clauses based on variables

I'm trying to add additional where clauses to a linq query depending on what variable results are passed to a function.
var allFeedback =
from f in _unitOfWork.Feedback.All()
join b in _unitOfWork.Bookings.All() on f.CourseBookingID equals b.CourseBookingID
join cb in _unitOfWork.CourseBookings.All() on f.CourseBookingID equals cb.CourseBookingID
where b.SiteID == siteID && b.Date >= fromDate && b.Date <= to && b.CancelledID == null
select f;
if (courseID > 0)
{
allFeedback.Where(f => f.CourseBooking.CourseID == courseID);
}
if (facilitatorID == 0)
{
allFeedback.Where(f => f.CourseBooking.FacilitatorID == null);
}
else if (facilitatorID > 0)
{
allFeedback.Where(f => f.CourseBooking.FacilitatorID == facilitatorID);
}
allFeedback.ToList();
I want to add the where clauses to the original query "allFeedback" but when the query is executed the additional clauses are ignored.
Is this possible?
yes its possible just do :
if (courseID > 0)
{
allFeedback = allFeedback.Where(f => f.CourseBooking.CourseID == courseID);
}
if (facilitatorID == 0)
{
allFeedback = allFeedback.Where(f => f.CourseBooking.FacilitatorID == null);
}
else if (facilitatorID > 0)
{
allFeedback = allFeedback.Where(f => f.CourseBooking.FacilitatorID == facilitatorID);
}
You just forgot to assign the result to the variable.

Return all in List <T> in lambda expression

I'm using lambda expression in LINQ where i have to get all the result when the conditon satisfies if not it should filter.
//Code
List<Dispatch> objDispatch = (List<Dispatch>)Session["Data"];
objDispatch = objDispatch.FindAll(dispatch => dispatch.CustomerTransName == ddlTransporterName.SelectedItem.Text && dispatch.InvoiceDate.Date >= Convert.ToDateTime(FromDate).Date && dispatch.InvoiceDate.Date <= Convert.ToDateTime(ToDate).Date);
In the above code i'm filtering the result set with some conditions in that first condition i need a help.
If the transporter name is 'ALL' it should return all the result set it matches with the Date condition or else it should return according to the TransporterName.
How can i achieve this?
With pure logic.
if(ddlTransporterName.SelectedItem.Text == "ALL") {
//return all
} else {
//Do your filter logic
}
Or, with less repitive code:
objDispatch = objDispatch.FindAll(
dispatch => (ddlTransporterName.SelectedItem.Text == "ALL" || dispatch.CustomerTransName == ddlTransporterName.SelectedItem.Text)
&& dispatch.InvoiceDate.Date >= Convert.ToDateTime(FromDate).Date
&& dispatch.InvoiceDate.Date <= Convert.ToDateTime(ToDate).Date);
string name = ddlTransporterName.SelectedItem.Text;
objDispatch = objDispatch.FindAll(dispatch =>
(name == "ALL" || dispatch.CustomerTransName == name)
&& dispatch.InvoiceDate.Date >= Convert.ToDateTime(FromDate).Date
&& dispatch.InvoiceDate.Date <= Convert.ToDateTime(ToDate).Date);
If transporter name is "ALL" then name OR condition will give true and CustomerTransName will not be checked.
If you want this to be LINQ then you need to actually use a LINQ method e.g. use Where. Also you should do your date conversions once outside if they aren't specific to the row, otherwise they will be converting everytime. Not only that, it makes for more readable code...
var selectedTransporter = ddlTransporterName.SelectedItem.Text;
var fromDate = Convert.ToDateTime(FromDate).Date;
var toDate = Convert.ToDateTime(ToDate).Date;
var query = objDispatch.Where(x => (selectedTransporter == "All" || x.CustomerTransName == selectedTransporter) && x.InvoiceDate.Date >= fromDate && x.InvoiceDate.Date <= toDate);
I think this should suffice:
List<Dispatch> objDispatch = (List<Dispatch>)Session["Data"];
List<Dispatch> filteredDispatches = objDispatch.Where(dispatch => dispatch.InvoiceDate.Date >= Convert.ToDateTime(FromDate).Date && dispatch.InvoiceDate.Date <= Convert.ToDateTime(ToDate).Date).ToList();
if (ddlTransporterName.SelectedItem.Text != "ALL")
{
filteredDispatches = filteredDispatches.Where(dispatch => dispatch.CustomerTransName == ddlTransporterName.SelectedItem.Text).ToList();
}
I think something like this should work:
List<Dispatch> objDispatch = (List<Dispatch>)Session["Data"];
var _fromDate = Convert.ToDateTime(FromDate);
var _toDate = Convert.ToDateTime(ToDate);
objDispatch = objDispatch
.FindAll(dispatch => Selector(
dispatch, ddlTransporterName.SelectedItem.Text, _fromDate, _toDate));
static bool Selector(
Dispatch dispatch, string name, DateTime fromDate, DateTime toDate)
{
if (dispatch.CustomerTransName == "ALL")
{
return dispatch.InvoiceDate.Date >= fromDate.Date
&& dispatch.InvoiceDate.Date <= toDate.Date;
}
return dispatch.CustomerTransName == name;
}

Categories

Resources