Remove a range of items from a list without looping - c#

Hi Is there a more ellegant way of doing this must I do the loop is there like a range funciton I could just remove all the items found
Sorry I should have showing how my qry is being inserted.
Btw these are two different entities that I am removing from I hope you get the idea.
var qry = db.AssemblyListItems.AsNoTracking().Where(x =>
x.ProductionPlanID == (long)_currentPlan.ProductionPlan ).ToList();
var hasbeenAssembled = db.CompletedPrinteds.AsNoTracking().Where(x =>
x.ProductionPlanId == item.ProductionPlanID).ToList();
var hasbeenFound = db.CompletedPrinteds.AsNoTracking().Where(x =>
x.ProductionPlanId== item.ProductionPlanID).ToList();
foreach (var subitem in hasbeenAssembled )
{
if(item.ProductionPlanID ==subitem.ProductionPlanId && item.DocumentNo == subitem.DocumentNo && item.DocumentNo == subitem.DocumentNo && item.OutstandingToMake ==0)
{
qry.RemoveAll(x => x.ProductionPlanID == subitem.ProductionPlanId && x.DocumentNo == item.DocumentNo && x.ItemCode == subitem.StockCode && item.OutstandingToMake ==0);
}
}
public List<AssemblyListItems> RemoveDespatchedItems(List<AssemblyListItems> AssemblyItems)
{
foreach (AssemblyListItems item in AssemblyItems)
{
using (var db = new LiveEntities())
{
var hasNotBeenDespatched = db.PalletizedItems.Where(w => w.Despatched != "Not Despatched");
foreach (var subitem in hasNotBeenDespatched)
{
AssemblyItems.RemoveAll(x => x.ProductionPlanID == subitem.ProductionPlanID && x.DocumentNo == item.DocumentNo && x.ItemCode == subitem.StockCode);
}
}
}
return AssemblyItems;
}
I just need to remove the items from the first query hasNotBeenDespatched from the second query.As could be over 400 items i want it to be efficient as possible.
Edit 2
I am a we bit closer thanks buts its still not removing the items from the removedespatchitems from the assebmittems I do not no why
public List<AssemblyListItems> RemoveDespatchedItems(List<AssemblyListItems> AssemblyItems, Int64 ProductionPlanId)
{
using (var db = newLiveEntities())
{
List<PalletizedItems> removeDespatchItems = db.PalletizedItems.Where(w => w.Despatched != "Not Despatched" && w.ProductionPlanID == ProductionPlanId).ToList();
var itemsDocumentNo = db.PalletizedItems.Select(x => x.ProductionPlanItemID).ToList();
foreach (var subitem in removeDespatchItems) {
AssemblyItems.RemoveAll(x => x.ProductionPlanID == subitem.ProductionPlanID && itemsDocumentNo.Contains(x.ProductionPlanItemID) && x.ItemCode == subitem.StockCode && x.LineQuantity==x.AllocatedQuantity);
}
}
return AssemblyItems;
}

Not 100% I get exactly how it should be.
However in general you could use join that would result in it being done in the database. Something like this:
var remainingItems = (from ali in db.FUEL_AssemblyListItems
join completed in db.FuelCompletedPrinteds
on new { ali.ProductionPlanID, ali.DocumentNo, ali.ItemCode } equals new { completed.ProductionPlanID, completed.DocumentNo, completed.StockCode }
join dispatched in db.FUEL_PalletizedItems
on new { ali.ProductionPlanID, ali.DocumentNo, ali.ItemCode } equals new { dispatched.ProductionPlanID, dispatched.DocumentNo, dispatched.StockCode }
where (ali.ProductionPlanID == (long) _currentPlan.ProductionPlan
&& ali.DocumentNo == completed.DocumentNo
&& completed.OutstandingToMake == 0
&& dispatched.Despatched != "Not Despatched")
select ali).ToList();
Depending upon the records in the database the join might need to be a outer join which needs a slightly different syntax but hopefully you've got a starting point.

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)

Searching in lists regarding to the database in ASP .NET in C#

this is a description of my task as it should be done
And that is my code. I would be grateful if someone can help me and explain, why my solution is not good.
public IList<MoneyTransfer> SearchTransfer(string clientAccountNo, decimal? amount,
string phrase, string beneficiaryAccountNo)
{
var listOfMoneyTransfers = new List<MoneyTransfer>();
var result = new List<MoneyTransfer>();
if (clientAccountNo is null)
{
return result;
}
else
{
foreach (BankAccount i in BankAccounts.ToList())
{
foreach (MoneyTransfer j in TransfersHistory.ToList())
{
if (i.AccountNo == clientAccountNo)
{
listOfMoneyTransfers.Add(j);
}
}
}
if (listOfMoneyTransfers.Count > 0)
{
foreach (var k in listOfMoneyTransfers)
{
if (k.Amount == amount || k.BeneficiaryAccountNo == beneficiaryAccountNo || k.Title == phrase)
{
result.Add(k);
}
}
}
}
return result;
}
Probably that is mistake during searching by three additional parameters, but I am not sure. Please for a help
private static string CorrectClientAccount =
"12345678901234567890000001";
[Test]
public void SearchTransferBy_BeneficiaryAccount()
{
var service = CreateService();
service.CreateTransfer(CorrectClientAccount, 250m,
"Transfer 1", "12345678901234567890000001");
service.CreateTransfer(CorrectClientAccount, 300m, "Other
2", "12345678901234567890000002");
service.CreateTransfer(CorrectClientAccount, 400m, "Other
3", "12345678901234567890000003");
service.CreateTransfer(CorrectClientAccount, 500m, "Other
4", "12345678901234567890000003");
var result = service.SearchTransfer(CorrectClientAccount,
null, "", "12345678901234567890000003");
Assert.IsNotNull(result);
Assert.AreEqual(2, result.Count);
Assert.IsTrue(result.Any(a => a.ClientAccountNo ==
CorrectClientAccount && a.Amount == 400m && a.Title ==
"Other 3" && a.BeneficiaryAccountNo ==
"12345678901234567890000003"));
Assert.IsTrue(result.Any(a => a.ClientAccountNo ==
CorrectClientAccount && a.Amount == 500m && a.Title ==
"Other 4" && a.BeneficiaryAccountNo ==
"12345678901234567890000003"));
}
That is unit test
This part of the code looks problematic:
foreach (BankAccount i in BankAccounts.ToList())
{
foreach (MoneyTransfer j in TransfersHistory.ToList())
{
if (i.AccountNo == clientAccountNo)
{
listOfMoneyTransfers.Add(j);
}
}
}
When looking trough bank accounts and money transfers, you do not check that transfer belongs to a particular account.
This code would add all of the money transfers to the list, if there is a bank account with this clientAccountNo, and not just the transfers belonging to the client.
Most likely, you need to do something like this (I can only guess for variable names I've not been provided with)
(this also assumes that MoneyTransfer doesn't have AccountNo)
var clientBankAccount = BankAccounts.FirstOrDefault(account => account.AccountNo == clientAccountNo);
// be sure to check that account has been found
foreach (MoneyTransfer j in TransfersHistory.ToList())
{
if (j.BankAccountId == clientBankAccount.Id)
{
listOfMoneyTransfers.Add(j);
}
}
I would first focus on the functional correctness of code and then we can also discuss the the efficiency.
Functional issue: There is no relationship between BankAccounts and TransferHistory list iterations. We are selecting all the transfer histories without filtering if those originate from the clientBankAccount as we are just matching the clinetAccountNumber with outer iteration item(BankAccount i), all the transfers are included if that condition matches. We should have filtered only those Tranfers that originated from the clientBankAccount.
foreach (BankAccount i in BankAccounts.ToList())
{
foreach (MoneyTransfer j in TransfersHistory.ToList())
{
if ***(i.AccountNo == clientAccountNo)***
{
listOfMoneyTransfers.Add(j);
}
}
}
There could have been many optimizations like:
Adding precondition checks like:
if (null == amount && string.IsNullOrWhilespace(beneficiaryAccountNo) && string.IsNullOrWhilespace(phrase))
{
return new List<MoneyTransfer>();
}
foreach (MoneyTransfer j in TransfersHistory.ToList())
{
if (j.AccountNo == clientAccountNo)
{
listOfMoneyTransfers.Add(j);
}
}
You don’t really need the outer loop, and instead be checking for j.ClientAccountNo == clientAccountNo, to get the list of transfers which belong to correct account.
foreach (var k in listOfMoneyTransfers)
{
if (k.Amount == amount || k.BeneficiaryAccountNo == beneficiaryAccountNo || k.Title == phrase)
{
result.Add(k);
}
}
This is also an issue, the expression is shortcuited once a condition is met, hence it might result in situations where it returns money transfers which match the amount, but doesn’t match the beneficiary account number or contain the search phrase.
Once a condition is met, the rest are not evaluated.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/boolean-logical-operators#conditional-logical-or-operator-
Consider using Linq where clause and conditional searching. something like
var results = listOfMoneyTransfers
.Where(x => amount.HasValue ? x == amount : true)
.Where(x => string.InNullOrWhiteSpace(phrase) ? x.Title.contains(phrase) : true)
.Where(x => string.InNullOrWhiteSpace(beneficiaryAccountNo) ? x.BeneficiaryAccountNo == beneficiaryAccountNo : true)
The multiple where clauses will be flatted into a single predicate https://github.com/dotnet/runtime/blob/a24364a09d9aea98b545f16689a53bafc6b18c14/src/libraries/System.Linq/src/System/Linq/Utilities.cs#L56

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

Tidying up lambda expressions

The following code gives me the results I need, but can anyone suggest how to write it better? I'm sure there's a more efficient way to do it.
Thanks.
private bool IsLocation(Guid _vID, Guid OrganisationId)
{
var vehicle = _vehilceRepository.GetSingle(c => c.vehicleId == _vID);
var clients = _clientRepository.GetList(c => c.OrganisationID == OrganisationId);
foreach (var client in clients)
{
var locations = _LocationRepository.GetList(c => c.ClientID == client.ClientID);
if (locations.Count > 0)
{
foreach (var location in locations)
{
if (location.LocationId == vehicle.LocationID)
{
return true;
}
}
}
}
return false;
}
var vehicle = _vehilceRepository.GetSingle(c => c.vehicleId == _vID);
var clients = _clientRepository.GetList(c => c.OrganisationID == OrganisationId);
return clients.SelectMany(client => _LocationRepository.GetList(
c => c.ClientID == client.ClientID))
.Any(location => location.LocationId == vehicle.LocationID);
?

How to write Linq expression using external objects

I am trying to convert a loop in to a linq expression. But it seams not to work the way i am doing it:
var customer = GetCustomerFromDatabase(id);
ICollection<Order> customerOrders = null;
if (customer == null)
{
LogAndThrowCustomerNotFound(id);
}
else
{
customerOrders = customer.Orders;
}
customer.YearToDateSales = 0.0;
customer.CurrentSales = 0.0;
DateTime today = DateTime.Now;
if (customerOrders != null)
foreach (var order in customerOrders)
{
if (order.SubmittedDate != null
&& order.SubmittedDate.Value.Year.CompareTo(today.Year) == 0)
{
customer.YearToDateSales += (double)order.OrderTotal;
}
if (order.SubmittedDate != null
&& (order.SubmittedDate.Value.Month.CompareTo(today.Month) == 0
&& order.SubmittedDate.Value.Year.CompareTo(today.Year) == 0))
{
customer.CurrentSales += (double)order.OrderTotal;
}
}
So I came up with that expression to get the customer orders that match the current year... bot it does not work. in he expression order is empty and today is conflicting. I i create
DateTime today = DateTime.Now; in the parm of the expression i get different errors...
IEnumerable<Order> cOrders = customerOrders
.Where((ICollection<Order> order , today) =>
order.SubmittedDate.Value.Month == today.Month);
It's simpler if you just don't attempt pass today into the lambda, it'll be closed into the expression anyway;
customer.YearToDateSales = customerOrders
.Where(x => x.SubmittedDate != null &&
x.SubmittedDate.Value.Year == today.Year)
.Sum(x => x.OrderTotal);
customer.CurrentSales = customerOrders
.Where(x => x.SubmittedDate != null &&
x.SubmittedDate.Value.Month == today.Month &&
x.SubmittedDate.Value.Year == today.Year)
.Sum(x => x.OrderTotal);
Hard to tell exactly what's wrong without the error, but you probably need to check for null on the SubmittedDate like in the original version:
IEnumerable<Order> cOrders = customerOrders
.Where((ICollection<Order> order , today) =>
order.SubmittedDate.HasValue &&
order.SubmittedDate.Value.Month == today.Month);

Categories

Resources