How to recursively call Where clause in Linq or SQL - c#

Let's say I have the following filter parameters:
Type="Student"
School = "High"
ReferenceID = "123abc"
PaymentOnFile= "Y"
Now, I need to find 1st student based on these 4 parameters. If no students are found then I need to find them based on the 3 parameters, if no students are found then use 2 parameters, etc.
Here's my current code:
var Student = db.Students.Where(x=> x.School == School && x.Type == Type && x.ReferenceID == ReferenceID && x.PaymentOnFile == PaymentOnFile).FirstOrDefault();
if (Student == null)
{
Student = db.Students.Where(x=> x.School == School && x.Type == Type && x.ReferenceID == ReferenceID).FirstOrDefault();
}
if (Student == null)
{
Student = db.Students.Where(x=> x.School == School && x.Type == Type).FirstOrDefault();
}
if (Student == null)
{
Student = db.Students.Where(x=> x.School == School).FirstOrDefault();
}
return Student;
This works but it is not very efficient and ugly. What is a better way to do this? Maybe using expression trees or something else but I cannot figure it out.
SQL also works!

I think something like this would work:
var student = db.Students
.Where(x => x.School == school)
.OrderBy(x => (x.Type == type) ? 0 : 1)
.ThenBy(x => (x.ReferenceID == referenceId) ? 0 : 1)
.ThenBy(x => (x.PaymentOnFile == paymentOnFile) ? 0 : 1)
.FirstOrDefault();

This dynamic solution will perform exactly your recursive logic and will work at case of Linq and EF. You can add another conditions(to predicates, order matters), solution(for loop) will remain the same.
var predicates = new List<Expression<Func<Student, bool>>>
{
x => x.School == "High",
x => x.Type == "Student",
x => x.ReferenceID == "123abc",
x => x.PaymentOnFile == "Y",
};
Student student = null;
for(var i = 0; i < predicates.Count; i++)
{
var query = db.Students.AsQueryable();
for (var j = 0; j < predicates.Count - i; j++)
query = query.Where(predicates[j]);
if ((student = query.FirstOrDefault()) != null)
break;
}

i guess if you could apply this query logic which can help you better
SELECT TOP 1
*
FROM Student AS S
ORDER BY CASE
WHEN S.School = #School AND S.[Type] = #Type AND S.ReferenceID = #ReferenceID AND S.PaymentOnFile = #PaymentOnFile THEN
1
WHEN S.School = #School AND S.[Type] = #Type AND S.ReferenceID = #ReferenceID THEN
2
WHEN S.School = #School AND S.[Type] = #Type THEN
3
WHEN S.School = #School THEN
4
END ASC

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)

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

Using a conditional if statement in a Linq query

I'm trying to write a linq query that uses an if statement.
In the code below I'm searching for matches of
n.SAU_ID = sau.SAUID where
ReportingPeriod column contains "Oct1" then
FiscalYear - aprYearDiff = sau.SAUYearCode.
Else
FiscalYear - octYearDiff = sau.SAUYearCode.
My code is only giving matches for the SAUID and "Oct1".
What code is needed to implement theese statements?
int FiscalYear = 2014;
List<String> addtowns = new List<string>();
List<Stage_Reorg> reorg = _entities.Stage_Reorg.ToList();
int aprYearDiff = 2;
int octYearDiff = 1;
foreach (var sau in reorg)
{
addtowns.AddRange(_entities.Stage_EPSSubsidySADCSDTown
.Where(n => n.SAU_ID == sau.SAUID
&& (n.ReportingPeriod == "Oct1"
? (FiscalYear - aprYearDiff) == sau.SAUYearCode
: (FiscalYear - octYearDiff) == sau.SAUYearCode))
.Select(n => n.TownCode ));
}
This is a bad idea anyway. Transform the condition to
(n.ReportingPeriod == "Oct1" && (FiscalYear - aprYearDiff) == sau.SAUYearCode)
|| (n.ReportingPeriod != "Oct1" && (FiscalYear - octYearDiff) == sau.SAUYearCode)
Here is a possible way but this probably won't work with EF. You will need to load all records into memory then perform the filtering:
addtowns.AddRange(_entities.Stage_EPSSubsidySADCSDTown
.Where(n => {
bool b = n.ReportingPeriod == "Oct1"
? (FiscalYear - aprYearDiff) == sau.SAUYearCode
: (FiscalYear - octYearDiff) == sau.SAUYearCode);
return b && n.SAU_ID == sau.SAUID;
}).Select(n => n.TownCode ))

Distance in SQL using Distance()

I'm trying to get the points in a database that are at a distance of no more than 200 meters away, and i'm using the following code but it also retrieves all the points in the data base
var pin = from pinsA in db.PINS
.Where(p => p.PRIVACY == 0 || p.USER_ID == userId
&& (double)currentPoint.Distance(p.location) < 200.0)
.Select(pr => new { pr.PIN_ID, pr.TYPE, pr.location })
select new { pinsA.PIN_ID, pinsA.TYPE, pinsA.location } ;
You don't provide the specific conditions but I think you are missing parenthesis in your Where clause, try:
Where((p => p.PRIVACY == 0 || p.USER_ID == userId)
&& (double)currentPoint.Distance(p.location) < 200.0)
Although its a preference thing, I'm not sure on the need to mix-match the query "languages" (which probably isn't helping), nor the need for a "double" select.
I also agree with jnovo's post on that you are most likely missing parenthesis in your where clause, but try either of the following:
Option 1:
var pinsWithin200 = from pins in db.Pins
where ((pins.PRIVACY == 0 || pins.USER_ID == userId) && pins.location.Distance(currentPoint) <= 200)
select new
{
PIN_ID = pins.PIN_ID,
TYPE = pins.TYPE,
location = pins.location
};
var results = pinsWithin200.ToList();
Option 2:
var pinsWithin200 = db.Pins
.Where(p => (p.PRIVACY == 0 || p.USER_ID == userId) && p.location.Distance(currentPoint) <= 200)
.Select(p => new
{
PIN_ID = p.PIN_ID,
TYPE = p.TYPE,
location = p.location
};
var results = pinsWithin200.ToList();
Whichever you prefer, they are cleaner, and without the double-select. Also note its good practise to explicitly name your properties in anonymous types.

How can i use "COALESCE(SUM..." in linq?

i try to use sum and Coalesce . How can i translate to linq?
SELECT #SumQtyOut=COALESCE(SUM(Qty),0) FROM dbo.StockMovement WHERE FromLocationType=#FromLocationType AND
* FromNo=#FromNo AND FromSeq=#FromSeq AND ItemTypeNo=#ItemTypeNo AND ItemID=#ItemID
i do sometihng :
using (StockProcedureDataContext stock = new StockProcedureDataContext())
{
SumQtyOut = from s in stock.StockMovements
where s.FromLocationType == FromLocationType &&
s.FromNo== FromNo &&
s.FromSeq == FromSeq &&
s.ItemTypeNo == ItemTypeNo &&
s.ItemID == ItemID select
}
This snippet should yield the result you are looking for.
using (StockProcedureDataContext stock = new StockProcedureDataContext())
{
var items = from s in stock.StockMovements
where s.FromLocationType == FromLocationType &&
s.FromNo== FromNo &&
s.FromSeq == FromSeq &&
s.ItemTypeNo == ItemTypeNo &&
s.ItemID == ItemID
select s.Qty ?? 0;
SumQtyOut = items.Sum(x => x);
}
select s.Qty ?? 0 returns 0 if s.Qty is null. items.Sum(x => x) summs up the quantities you have selected.

Categories

Resources