Iterate over an IQueryable without calling ToList() - c#

I have a DB used for a production line. It has an Orders table, and Ordertracker table, an Item table, and an Itemtracker table.
Both Orders and Items have many-to-many relationships with status. The tracker tables resolves these relationships in such a way that an item can have multiple entries in the tracker - each with a particular status.
I tried to upload a picture of the tables to make things clearer but alas, I don't have enough points yet :C
I need to find items whose last status in the Itemtracker table meets a condition, either '3' or '0'.
I then need to get the first one of these items.
The steps I am using to accomplish this are as follows:
Get all the Orders which have a certain status.
Get all the Items in that Order.
Get all the Items whose last status was = 0 or 3.
Get the first of these items.
My code is as follows:
public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
{
var firstOrder = GetFirstOrderInProductionAndNotCompleted();
var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;
if (CheckStatusOfItems(items) != null)
{
var nextItem = CheckStatusOfItems(items);
return nextItem ;
}
else
{
return null;
}
}
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
List<ITEM> listOfItemsToProduce = new List<ITEM>();
foreach (ITEM item in items.ToList())
{
var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();
if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
{
listOfItemsToProduce.Add(item);
}
}
return listOfItemsToProduce.FirstOrDefault();
}
Now, this all works fine and returns what I need but I'm aware that this might not be the best approach. As it is now my IQueryable collection of items will never contain more than 6 items - but if it could grow larger, then calling ToList() on the IQueryable and iterating over the results in-memory would probably not be a good idea.
Is there a better way to iterate through the IQueryable items to fetch out the items that have a certain status as their latest status without calling ToList() and foreaching through the results?
Any advice would be much appreciated.

Using LINQ query syntax, you can build declaratively a single query pretty much the same way you wrote the imperative iteration. foreach translates to from, var to let and if to where:
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
let lastStatusOfItem = ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.FirstOrDefault()
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
or alternatively using from instead of let and Take(1) instead of FirstOrDefault():
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
from lastStatusOfItem in ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.Take(1)
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}

Related

Getting a List<Items> out of List<List<Items>>

I have a object called Items. I use that object like so.
List<List<Items>> myItems = new List<List<Items>>();
I want to know how to get a specific List<Items> out of the List<List<Items>> object. Currently I am using foreach loops with some rules to find a specific List<Items>
This is currently what I am doing
foreach (List<Items> db2Items in db2Data)
{
foreach (List<Items> apiItems in apiData)
{
if (db2Items[0].Value == apiItems[0].Value && db2Items[27].Value == apiItems[27].Value)
{
/// Some other logic here
}
}
}
I was wanting to use LINQ to get the matching List<items> out of apiData and if I have a result then do the logic I wanted to do.
Not really sure what you're trying to accomplish, but if you want to get a list in a list based on a certain condition you can do it like this:
var item = db2Data.Where(x => x.Where(y => y.[value] == [YourCondition]).Any()).FirstOrDefault();
the "x.Where(y => y == [YourCondition]).Any()" will return true if the condition is met, and firstordefault will then return the list that meets the condition.
I have figured it out.
Using LINQ:
foreach (List<Items> db2Items in db2Data)
{
IEnumerable<List<Items>> theItem = from item in apiData
where item[0].Value == db2Items[0].Value && item[27]>Value == db2Items[27].Value
select item;
if (theItem.ToList().Count > 0)
{
// Do something
}
}

Is there a faster way to do this LINQ?

I have a loop inside my program, which loops through thousands of object to find the right one with particular id.
is there any better and faster way than this
int id;
SPList list = SPContext.Current.Web.Lists.TryGetList("DataLibrary");
IEnumerable<SPListItem> _dataitems = list.Items.OfType<SPListItem>();
foreach (SPListItem item in _dataextantitems)
{
if (item.ID == id)
{
title= item.Title;
}
}
Use the GetItemById of SPList.
var title = SPContext.Current.Web.Lists["DataLibrary"].GetItemById(id).Title;
If your list has a lot of columns, and you want to avoid pulling them all down, you can pull down just the Title column instead:
var title = SPContext.Current.Web.Lists["DataLibrary"]
.GetItemByIdSelectedFields(id, "Title").Title;
Now if you really want to use LINQ here you could use LINQ to Sharepoint, but it's not actually going to simplify the code a ton. After using SPMetal.exe to generate a file based on your lists, you'd be able to write:
using(var context = new YourContextNameHere(SPContext.Current.Site.Url))
{
var title = context.DataLibrary
.Where(item => item.ID == id)
.Select(item => item.Title)//to avoid pulling down other columns
.First();
}
Make sure your list is sorted. Then you can use the BinarySearch method of the list or write your own implementation. If not you can shorten your code using linq.
var itemToLookup = list.Items.OfType<SPListItem>().FirstOrDefault(x => x.ID == id);
if (itemToLookup != null)
{
//...
}

Using C# lambda to find not matching elements in collection

I have two collections that I get from functions:
IEnumerable<InventoryItem> inventoryItems = get();
IEnumerable<InventoryItem> relatedItems = get();
I want to assign related items to each inventory item. But, related item can't match the inventory item itself. Meaning inventory item cant have itself for related item.
I am trying to skip the overlapping elements in the collection this way:
foreach (var item in inventoryItems)
{
InventoryItem item1 = item;
relatedItems.SkipWhile(x => x.RelatedItems.Contains(item1)).ForEach(i => item1.RelatedItems.Add(i));
Save(item);
}
This does not seem to work. Do any of you Linq user have any better suggestions.
The problem that I have is with SkipWhile(x => x.RelatedItems.Contains(item1)) part. The other part works when matching items regardless if they overlap
Where with negative condition should filter out the only item you don't need (note that comapison with != may need to be replaced with some other condition that check item identity)
item1.RelatedItems = relatedItems
.Where(x => !x.RelatedItems.Any(r => r!= item1)).ToList();
Try this:
public IEnumerable<T> GetNotMatchingElements<T>(IEnumerable<T> collection1, IEnumerable<T> collection2)
{
var combinedCollection = collection1.Union(collection2);
var filteredCollection = combinedCollection.Except(collection1.Intersect(collection2));
return filteredCollection;
}
Not sure I completely understand, but if I do, this should work:
foreach (var invItem in inventoryItems)
{
invItem.RelatedItems = relatedItems
.Where(relItem => !relItem.RelatedItems.Contains(invItem)));
Save(invItem);
}

How to change value of an object using linq

I have the following statment that if isdefault is true to this collection i need to set each object isDefault property to false.
custHead.lstCustomziation.Where(x => x.IsDefaultSelected == true).Select(x=>{x.IsDefaultSelected=false});
lstCustomziation is a collection.
LINQ is for querying. You should use a foreach loop to make changes:
foreach (var item in custHead.lstCustomziation.Where(x => x.IsDefaultSelected))
{
item.IsDefaultSelected = false;
}
That said, if IsDefaultSelected is false for the other items anyway, it may be simpler just to unconditionally set it:
foreach (var item in custHead.lstCustomziation)
{
item.IsDefaultSelected = false;
}
Linq is for querying, not updating. You can get a list of the items you want to change and then update using a normal loop:
var list = custHead.lstCustomziation.Where(x => x.IsDefaultSelected == true)
foreach(var item in list)
item.IsDefaultSelected=false;
As the Q of LINQ says, LINQ is designed for queries, not updates.
Just enumerate the LINQ result and apply your update.
Linq may have been initially created for querying but it has evolved and is used as functional programming methods, equivalents to "map", "reduce", and "filter" used in other languages.
In your example I would suggest:
var list = custHead.lstCustomziation.Where(x => x.IsDefaultSelected == true)
.Select(x=> TransformItem(x));
private XType TransformItem(XType item){
item.IsDefaultSelected=false;
return item;
}

EF many-to-many madness

I have a method that updates a ReportRecipient object in EF. The primitives work fine; the headache comes in when trying to manage a M2M relationship with the RecipientGroups objects.
Please take a look at this code:
public IReportRecipient ModifyRecipientWithGroupAssignments(IEnumerable<Guid> groupIds, IReportRecipient recipient)
{
var entity = Context.ReportRecipients
.Include("RecipientGroups")
.FirstOrDefault(e => e.ReportRecipientId == recipient.ReportRecipientId)
.FromIReportRecipient(recipient);
var toRemove = entity.RecipientGroups
.Where(e => !groupIds.Contains(e.GroupId))
.ToList();
//remove group assignments that no longer apply
foreach (var group in toRemove)
{
if (group != null)
{
entity.RecipientGroups.Attach(group);
entity.RecipientGroups.Remove(group);
}
}
var toAdd = entity.RecipientGroups
.Where(e => groupIds.Contains(e.GroupId))
.ToList();
//add new groups that weren't there before
foreach (var group in toAdd)
{
if (group != null)
{
entity.RecipientGroups.Attach(group);
}
}
return entity;
}
... my problem is on the var ToAdd... line. Even if I have a collection of Guids in groupIds that match Guids representing RecipientGroup objects in the database, toAdd always evaluates to an empty collection. I would think the Contains() function would work for this scenario; can someone please explain if I am doing something wrong?
You should load the RecipientGroups you want to add from the database (Context.RecipientGroups I guess), not from the collection you want to add them to (entity.RecipientGroups in the code sample).

Categories

Resources