Avoiding repeating code with Linq query + optional params - c#

Given the code:
/// <summary>
/// Get a games high scores
/// </summary>
public static List<Score> GetGameHighScores(int gameID, HighScoreType type, int? skip = null, int? take = null)
{
var r = new List<Score>();
using (var db = new ArcadeContext())
{
var q = new List<ArcadeScore>();
if (skip != null && take != null)
{
switch (type)
{
case HighScoreType.ScoreRank:
q =
db.ArcadeScores.Where(c => c.GameID == gameID && c.ScoreRank > 0)
.OrderBy(c => c.ScoreRank)
.Skip(skip.Value)
.Take(take.Value)
.ToList();
break;
case HighScoreType.UserRank:
q =
db.ArcadeScores.Where(c => c.GameID == gameID && c.UserRank > 0)
.OrderBy(c => c.UserRank)
.Skip(skip.Value)
.Take(take.Value)
.ToList();
break;
}
}
else
{
switch (type)
{
case HighScoreType.ScoreRank:
q =
db.ArcadeScores.Where(c => c.GameID == gameID && c.ScoreRank > 0)
.OrderBy(c => c.ScoreRank)
.ToList();
break;
case HighScoreType.UserRank:
q =
db.ArcadeScores.Where(c => c.GameID == gameID && c.UserRank > 0)
.OrderBy(c => c.UserRank)
.ToList();
break;
}
}
r.AddRange(q.Select(arcadeScore => new Score(arcadeScore)));
}
return r;
}
Where skip and take are optional paramters (used for when pagination is required), what's the best way to fetch the correct records without repeating myself like above?

Instead of switching between four separate queries, build single query according to your conditions:
IQueryable<Score> query = db.ArcadeScores.Where(c => c.GameID == gameID);
switch(type)
{
case HighScoreType.ScoreRank:
query = query.Where(c => c.ScoreRank > 0).OrderBy(c => c.ScoreRank);
break;
case HighScoreType.UserRank:
query = query.Where(c => c.UserRank > 0).OrderBy(c => c.UserRank);
break;
}
if (skip.HasValue && take.HasValue)
query = query.Skip(skip.Value).Take(take.Value);
return query.ToList();

Given that the IEnumerable is not executed until you call ToList you can simply move those outside the switch statement and then call ToList

Simply use the coalesce operator to provide default values:
.Skip(skip ?? 0)
.Take(take ?? int.MaxValue)

Probably, extract this to a method so that you would only call OrderBy and Skip when they want the paginated one.
db.ArcadeScores.Where(c => c.GameID == gameID && c.ScoreRank > 0)
.OrderBy(c => c.ScoreRank).
db.ArcadeScores.Where(c => c.GameID == gameID && c.UserRank > 0)
.OrderBy(c => c.UserRank)
Into
private IEnumerable<ArcadeScore> GetArcadeOverallScore(int gameId)
{
return db.ArcadeScores.Where(c => c.GameID == gameID && c.ScoreRank > 0)
.OrderBy(c => c.ScoreRank)
}
private IEnumerable<ArcadeScore> GetArcadeUserScore(int gameId)
{
return db.ArcadeScores.Where(c => c.GameID == gameID && c.UserRank > 0)
.OrderBy(c => c.UserRank)
}

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)

LINQ Query with method syntax

My requirement is to make boolean value (IsPC=true) only if I found any value with IsCurrent = true from the list and second condition is to filter the list with G or W codes and third condition is to check the PCBNumber length ==15 with only one from the list.
How short can i able to reduce the below query using LINQ method syntax
below is my query
var CurrentQ= p.List.Where(x => x.IsConCurrent== true);
if (CurrentQ.Count() > 0)
{
var NCurrentQwithWorQ = p.List.Where(x => x.Codes == Codes.W|| x.Codes== Codes.Q).Count();
if (NCurrentQwithWorQ != null)
{
var PCBNumber = p.List.Where(x => x.PCBNumber .Length == 15).Count();
if (PCBNumber == 1)
{
isPC = true;
}
}
}
You can use all conditions in same query like below,
var PCBNumber= p.List.Where(x => x.IsConCurrent== true && (x.Codes == Codes.W|| x.Codes== Codes.Q) && x.PCBNumber.Length == 15);
if (PCBNumber !=null && PCBNumber.Count() == 1)
{
isPC = true;
}
I'm not trying to debug what you wrote, but isn't this really what you're looking for--that is, daisy-chaining your Where conditions?
var isPC = p.List.Where(x => x.IsConCurrent == true).Where(x => x.Codes == Codes.W || x.Codes == Codes.Q).Where(x => x.PCBNumber.Length == 15).Count() == 1;
Both solutions suggested above are correct.
p.List.Where(x => x.IsConCurrent== true && (x.Codes == Codes.W|| x.Codes== Codes.Q) && x.PCBNumber.Length == 15);
p.List.Where(x => x.IsConCurrent == true).Where(x => x.Codes == Codes.W || x.Codes == Codes.Q).Where(x => x.PCBNumber.Length == 15).Count()
Actually they are performed in the same way. The Where function does not force immediate iteration through the data source. Only when you execute the Count function, LINQ will process row by row and execute criterion by criterion to find out which values should be calculated.
I can only suggest you add the Take(2) operator after the where clause. In this case LINQ will stop after finding the first two rows that matches provided criterion and other rows will not be processed.
p.List.Where(x => x.IsConCurrent == true)
.Where(x => x.Codes == Codes.W || x.Codes == Codes.Q)
.Where(x => x.PCBNumber.Length == 15)
.Take(2).Count()

LINQ using Contains with entity

Am using ASP.NET MVC 5 to create an Application for billing, now i have thing function which receive a filter object with different variables, am having a problem with contains when i search, what am i doing wrong
public static List<Quote> getCustomerQuotes(QuoteFilter filter)
{
using (var db = new AppDBContext())
{
var q = db.Quotes.Where(u => u.entryDate > 0); ;
if (filter.type != null)
{
q = q.Where(u => u.quoteType == filter.type);
}
if (filter.only_permitable != null)
{
q = q.Where(u => !Values.NON_PERMITABLE_QUOTES.Contains(u.quoteType));
}
if (filter.quote_status != null)
q = q.Where(u => u.quote_status == (int)filter.quote_status);
if (filter.quotenumber != null)
{
q = q.Where(u => u.quote_number.Contains(filter.quotenumber));
}
if (filter.permitnumber != null)
q = q.Where(u => u.permit_number.Contains(filter.permitnumber));
if (filter.permit_status != null)
q = q.Where(u => u.permit_status == (int)filter.permit_status);
if (filter.quoteId != null)
q = q.Where(u => u.Id == (int)filter.quoteId);
if (filter.customer_id != null)
q = q.Where(u => u.customer_id == (int)filter.customer_id);
q = q.OrderByDescending(u => u.Id);
FileLogger.Log("getCustomerQuotes", q.ToString());
return q.ToList();
}
}
When i call the function and pass quotenumber, the contains doesnt search, it returns nothing
You have to evaluate your expression, before you apply the OrderByDescending.
q = q.Where(u => u.quote_number.Contains(filter.quotenumber)).ToList();
This should be happen also to the rest places.
Is quote number alpha-numeric? If yes, as Contains is case sensitive can you try comparison by first turning source and target to same case ? like
q = q.Where(u => u.quote_number.ToLower().Contains(filter.quotenumber.ToLower()));
Cheers
Ok, am answering my own question after finding a solution or i may call it a hack
public static List<Quote> getCustomerQuotes(QuoteFilter filter)
{
using (var db = new AppDBContext())
{
var q = db.Quotes.Where(u =>
(filter.type != null ? u.quoteType == filter.type : u.quoteType > 0) &&
(filter.only_permitable != null ? !Values.NON_PERMITABLE_QUOTES.Contains(u.quoteType) : u.permitType > 0) &&
(filter.quote_status != null ? u.quote_status == filter.quote_status : u.quote_status > -100) &&
(!string.IsNullOrEmpty(filter.quotenumber) ? u.quote_number.Contains(filter.quotenumber) || u.groupName.Contains(filter.quotenumber) : u.quoteType > 0) &&
(!string.IsNullOrEmpty(filter.permitnumber) ? u.permit_number.Contains(filter.permitnumber) || u.groupName.Contains(filter.permitnumber) : u.quoteType > 0) &&
(filter.permit_status != null ? u.permit_status == filter.permit_status : u.quoteType > 0) &&
(filter.quoteId != null ? u.Id == filter.quoteId : u.Id > 0) &&
(filter.customer_id != null ? u.customer_id == filter.customer_id : u.customer_id > -1)
).OrderByDescending(u => u.Id);
//FileLogger.Log("getCustomerQuotes", q.ToString());
return q.ToList();
}
}
i dont know why it didn't work the first time but now it works.

C# use left outer join in LINQ

This is my method:
public IEnumerable<Web_Vendor> GetSaleBrands()
{
using (var _db = new LuxedecorContext())
{
IQueryable<Web_Vendor> web_vendors = _db.Web_Vendor.
Where(x =>
(_db.Web_Promotion.Where(z => z.VendorID != string.Empty
&& z.Static == true && z.Percent != 0 &&
(x.WebPromotion.Expires >= DateTime.Now || x.WebPromotion.Expires == null))
.Select(r => r.VendorID).Contains(x.VendorID))
||
(_db.NavItems.Where(y => x.WebPromotion.VendorID == y.SC1
&& !(y.Promotion == "" || y.Promotion == null)
&& (y.PromotionStart <= DateTime.Now) && (y.PromotionEnd >= DateTime.Now || y.PromotionEnd == null))
.Select(g => g.SC1).Contains(x.WebPromotion.VendorID)))
.Where(x => x.Active == true).OrderBy(x => x.NameExtra)
.ThenBy(x => x.Sequence);
var a = web_vendors.ToList(); // 16 values
var b = web_vendors.Include(x => x.WebPromotion).ToList(); // 13 values
}
throw new NotImplementedException();
}
I need to Include nullable values to my collection of Web_Vendors. I know, it's famous question, but I need to return collection of Web_Vendors instead of collection of dynamic types. Is it possible?

how to show result using multiple Order by descending using linq query

I am having 3 criteria. I want to order these 3 types.
Who Paid with Master User
Who Update their Post Latest Date
Who Paid with Sub Master User
which one is having count it will come to top 15 Jobs.
My code here:
var orderMaster= _vasRepository.GetOrderDetails()
.Where(od => od.OrderMaster.OrganizationId != null &&
od.OrderId == od.OrderMaster.OrderId &&
od.OrderMaster.PaymentStatus == true &&
od.ValidityTill.Value >= currentdate)
.OrderByDescending(od => od.ValidityTill)
.Select(ord => ord.OrderMaster.Id.Value);
var updatedVacancyList = _repository.GetJobs()
.Where(c => c.UpdatedDate != null &&
updateFresh <= c.UpdatedDate)
.Select(c => c.Id);
var orderLatestUser = _vasRepository.GetOrderDetails()
.Where(od => od.OrderMaster.UserId != null &&
od.OrderMaster.PaymentStatus == true &&
freshUser <= od.ActivationDate &&
od.ValidityTill.Value >= currentdate)
.Select(c => c.OrderMaster.User.Id);
Then I check the count of those then assign to
List<int> lstMasterId = orderOrganization.ToList();
List<int>lstUpdatedJobsListId = updatedVacancyList.ToList();
List<int>lstUserListId= orderLatestUser.ToList();
Here i order the lists using query
Func<IQueryable<Job>, IOrderedQueryable<Job>> orderingFunc = query =>
{
if (orderMaster.Count() > 0)
return query.OrderByDescending(rslt =>
lstOrganizationId.Contains(rslt.OrganizationId))
.ThenByDescending(rslt=>lstUserListId.Contains(rslt.User.Id))
.ThenByDescending(rslt => lstUpdatedJobsListId.Contains(rslt.Id))
.ThenByDescending(rslt => rslt.CreatedDate);
else
return query.OrderByDescending(rslt => rslt.CreatedDate);
};
jobs = orderingFunc(jobs);
}
I want to show the lstUserListId at top of the result.. How to do this?

Categories

Resources