Change loop to Linq C# - c#

One of my function in C# required me to filter some value.So, I try it by using a lot of loop in it. It works but doesn't look like effecient.Any idea on how to convert code below to LinQ?
Any help is appreciated.
var object1 = JsonConvert.DeserializeObject<List<string>>(object1json);
foreach (var item1 in table1)
{
if (item1.Code == InputCode)
{
for (int i = 0; i < object1.Count(); i++)
{
tempData temp = new tempData();
foreach (var item2 in item1.List)
{
if (item2.Code == object1[i])
{
temp.Code = item2.Code;
temp.Description = item2.Description;
}
}
if(temp.Code != null || temp.Description != null)
final.Add(temp);
}
}
}

If you want your code to be more efficient, as pointed out in the comments, converting it to Linq isn't really going to help. It's still the same logic, just written in a different way. If you're going for readability, it can be improved with just a few changes:
foreach (var item1 in table1.Where(i => i.Code == InputCode))
{
foreach (var code in object)
{
// This could be SingleOrDefault, I don't know if you have duplicates in the list or not
var item2 = item1.List.LastOrDefault(i => i.Code == code);
if(item2 != null)
{
final.Add(new tempData
{
Code = item2.Code,
Description = item2.Description,
});
}
}
}
If you convert the whole thing to Linq:
var final = table1.Where(i => i.Code == InputCode)
.SelectMany(item1 => object.Select(code => item1.List.LastOrDefault(i => i.Code == code))
.Where(item2 => item2 != null)
.Select(item2 => new tempData
{
Code = item2.Code,
Description = item2.Description,
})
.ToList();
Personally, I prefer the first option, as it's a bit easier to read.

I guess what you post is sample code instead of actual code otherwise it would be better to avoid keyword like object in C#. Anyway,
var final = table1.Where(item1 => item1.Code == InputCode)
.SelectMany(item1 => item1.List)
.Where(item2 => #object.Contains(item2.Code))
.Where(temp => temp.Code != null || temp.Description != null)
.Select(item2 => new tempData()
{
Code = item2.Code,
Description = item2.Description
});

Related

C# LINQ Contains() query for autocomplete search box is slow

I've got a search box that I'm providing autocomplete suggestions for but it's really slow, it takes multiple seconds for suggestions to appear. I'm pretty sure my code is inefficient but I'm not sure the best way to improve it, any suggestions?
[HttpPost]
[Route("search")]
public virtual JsonResult Search(string term)
{
var result = new List<SearchResult>();
if (!String.IsNullOrWhiteSpace(term))
{
var searchTerms = term.ToLower().Split(' ');
List<Card> resultList = null;
foreach (var query in searchTerms)
{
if (resultList == null)
{
resultList = CardRepository.FindAll().Where(x => x.Name.ToLower().Contains(query) || x.Set.SetName.ToLower().Contains(query) || x.Variant.ToLower().Contains(query)
|| x.CardNumber.ToLower().Contains(query) || (query == "holo" && x.IsHolo)).ToList();
}
else
{
resultList = resultList.Where(x => x.Name.ToLower().Contains(query) || x.Set.SetName.ToLower().Contains(query) || x.Variant.ToLower().Contains(query)
|| x.CardNumber.ToLower().Contains(query) || (query == "holo" && x.IsHolo)).ToList();
}
}
foreach (var item in resultList.Take(10))
{
result.Add(new SearchResult()
{
label = item.FullCardName,
value = item.CardId.ToString()
});
}
}
return Json(result);
}
EDIT: Added the FindAll() code.
private readonly IDatabase _database;
public IQueryable<Card> FindAll()
{
return _database.CardDataSource.OrderBy(a => a.Name).AsQueryable();
}
SOLUTION: Going on the advice from the comments and with reference to this post Full Text Search with LINQ I moved my searching to the repository as a method and the result is almost instant autocomplete suggestions. I'm not sure how much better I could make the performance but it's easily usable in its current state.
public Card[] Search(string[] searchTerms)
{
IQueryable<Card> cardQuery = _database.CardDataSource;
foreach(var term in searchTerms)
{
var currentTerm = term.Trim();
cardQuery = cardQuery.Where(p => (p.Name.Contains(currentTerm) ||
p.Variant.Contains(currentTerm) ||
p.CardNumber.Contains(currentTerm) ||
p.Set.SetName.Contains(currentTerm) ||
(term == "holo" && p.IsHolo) ||
(term == "reverse" && p.IsHolo))
);
}
return cardQuery.Take(10).ToArray();
}
[HttpPost]
[Route("search")]
public virtual JsonResult Search(string term)
{
var result = new List<SearchResult>();
if (!String.IsNullOrWhiteSpace(term))
{
var searchTerms = term.ToLower().Split(' ');
var resultList = CardRepository.Search(searchTerms);
foreach (var item in resultList)
{
result.Add(new SearchResult()
{
label = item.FullCardName,
value = item.CardId.ToString()
});
}
}
return Json(result);
}
I think that the main problem is that you're using .FindAll() which returns a List<T>.
This means that when you say CardRepository.FindAll() it gets all of the records into an in-memory list and then your subsequent refining queries (e.g. Where(x => x.Name.ToLower().Contains(query)) and so on) are all run against the entire list. So it's right that it's returning really slowly.
You could try rewriting it by simply removing the .FindAll() and see what happens.
Please note, I'm just giving you the main problem, there are other issues, but none is as important as this one.
You could use multi-threading like this (pseudo-C# code):
var allCards = CardRepository.FindAll().ToArray(); // Ensure array.
query = query.ToUpper();
var nameTask = Task.StartNew(() => allCards.Where(x => x.Name.ToUpper().Contains(query)).ToArray());
var setTask = Task.StartNew(() => allCards.Where(x => x.Set.SetName.ToUpper().Contains(query)).ToArray());
var variantTask = Task.StartNew(() => allCards.Where(x => x.Variant.ToUpper().Contains(query)).ToArray());
var cardNumberTask = Task.StartNew(() => allCards.Where(x => x.CardNumber.ToUpper().Contains(query)).ToArray());
var holoTask = Task.StartNew(() => allCards.Where(x => query == "holo" && x.IsHolo).ToArray());
Task.WaitAll(new Task[] {nameTask, setTask, variantTask, cardNumberTask, holoTask});
var result = (nameTask.Result + setTask.Result + variantTask.Result + cardNumberTask.Result + halaTask.Result).Distinct().ToArray();

Remove a range of items from a list without looping

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.

Error while removing item from collection

Getting error Collection was modified; enumeration operation may not execute.
var toUpdateItm = MC_CRM_T001A.ItemDetails
.Where(X => X.CatNo == SelectedCRM_T001A.CatNo);
foreach (var itm in toUpdateItm)
{
int x = MC_CRM_T001A.PartDetails.IndexOf(MC_CRM_T001A.PartDetails
.Where(X => X.cat_item_id == itm.id)
.FirstOrDefault()
);
if (x >= 0 && x!=null)
{
MC_CRM_T001A.PartDetails.RemoveAt(x);
}
}
foreach (var itm in toUpdateItm)
{
if (itm.CatNo == SelectedCRM_T001A.CatNo)
{
MC_CRM_T001A.ItemDetails.Remove(itm);
}
}
You can't modify the list you're looping over. Change for foreach calls to foreach (var itm in toUpdateItem.ToList()), which will create a copy of the list, instead.
Also, you can express this code more cleanly without all the IndexOf stuff:
var toUpdateItm = MC_CRM_T001A.ItemDetails.Where(X => X.CatNo == SelectedCRM_T001A.CatNo).ToList();
foreach (var itm in toUpdateItm.ToList())
{
var item = MC_CRM_T001A.PartDetails.FirstOrDefault(X => X.cat_item_id == itm.id);
if (item != null) { MC_CRM_T001A.PartDetails.Remove(item); }
if (itm.CatNo == SelectedCRM_T001A.CatNo) { MC_CRM_T001A.ItemDetails.Remove(itm);
}
You don't need two loops either.

modify items in a Generic List

i cannot modify generic List with :
var x = (PaypalResponse)Session["PaypalResponse"]; // x.Response is my List
x.Response.ToList().Where(i => i.Id== 1).ForEach(s => s.Selected = true);
where am I doing wrong?
Thanks.
You could do this:
x.Response.Where(i => i.Id == 1).ToList().ForEach(s => s.Selected = true);
However, it's a bit of a waste of resources to construct a new list just for this one line of code. I'd recommend this instead:
foreach(var s in x.Response.Where(i => i.Id == 1))
{
s.Selected = true;
}
If you only want to update at most one item, you can do this instead:
var s = x.Response.FirstOrDefault(i => i.Id == 1);
if (s != null)
{
s.Selected = true;
}
And of course, if you know there will be one item to update, it's even easier:
x.Response.First(i => i.Id == 1).Selected = true;

Entity many to many check if relation exists

I have two tables: Items and Colors. They have a many to many relation. In a CheckBoxList that displays colors, I want to check those that are associated to the item shown.
using (var db = new ProwebModel.Entities())
{
var colors = db.Colors;
foreach (ListItem color in ((CheckBoxList)(fv.FindControl("cblColors"))).Items)
{
var itemId = Convert.ToInt32(Request.QueryString["id"]);
var colorNumber = Convert.ToInt32(color.Value);
color.Selected = colors.Where(t => t.ColorNumber == colorNumber).First().Items.Where(t => t.ItemId == itemId).Count() > 0;
}
}
This works fine, but I was wondering about this line :
color.Selected = colors.Where(t => t.ColorNumber == colorNumber).First().Items.Where(t => t.ItemId == itemId).Count() > 0;
Is there any better way to check if the association exist?
Thank you!
EDIT
I chaged my code to something better.. I think. Is there still a better way to do this?
using (var db = new ProwebModel.Entities())
{
var itemId = Convert.ToInt32(Request.QueryString["id"]);
var ItemColors = db.Items.First(t => t.ItemId == itemId).Colors.ToList();
foreach (ListItem color in ((CheckBoxList)(fv.FindControl("cblColors"))).Items)
{
var colorNumber = Convert.ToInt32(color.Value);
color.Selected = ItemColors.Where(t => t.ColorNumber == colorNumber).Count() > 0;
}
}
Many thanks!
Code update
using (var db = new ProwebModel.Entities())
{
var itemId = Convert.ToInt32(Request.QueryString["id"]);
var ItemColors = db.Items.First(t => t.ItemId == itemId).Colors.ToList();
foreach (ListItem color in ((CheckBoxList)(fv.FindControl("cblColors"))).Items)
{
var colorNumber = Convert.ToInt32(color.Value);
color.Selected = ItemColors.Any(t => t.ColorNumber == colorNumber);
}
}
The line could at least be rewritten as :
color.Selected = colors.First(t => t.ColorNumber == colorNumber).Items.Any(t => t.ItemId == itemId);

Categories

Resources