Working with Entity Framework, but that\'s probably irrelevant If I have an Iqueryable, how do I filter a sub list and keep it IQueryable so it doesn\'t yet hit the DB?
I think what you are looking is SelectMany. As an example for your case is something like this:
positiveItems = items.SelectMany(x => x.SubItem).Where(x=> x.ID == 1).Select(x=>x.Item);
//items still IQueryable , so we can concat it with another IQueryable
negativeItems = items.SelectMany(x=>x.SubItem).Where(x=>x.ID != 1).Select(x=>x.Item);
//just an using option
allItems = positiveItems.Concat(negativeItems);
And just a suggestion. For high number of reference object set, you can use ValueInjecter It is very simple and fast. I used it couple of production projects and it saved my tons of times.
I interpret your question as you want to return all Items
no matter what, but you want to filter SubItems
. There is no good way to say "I want to return this object except I want a modified version of X property" for an IQueryable
. You'll have to use a select statement where you select a new object if you want to this.
Option 1: Return the data separately
var itemsAndSubItems = items
.Select(item => new
{
Item = item,
SubItems = item.SubItems.Where(sub => sub.ID = 1)
}
);
or if you don't mind eagerly loading the items into memory:
IEnumerable<Item> = items
.Select(item => new
{
Item = item,
SubItems = item.SubItems.Where(sub => sub.ID = 1)
}
)
.ToList()
.Select(row =>
{
var item = row.Item;
item.SubItems = row.SubItems;
return item;
}
);
Option 2: Return a new instance of your class (which it seems you don't want to do)
IQueryable<Item> items = items
.Select(item => new Item
{
SubItems = item.SubItems.Where(sub => sub.ID == 1),
OtherProp = item.OtherProp
/*etc for the other properties on Item*/
}
);
Option 3: Add another property to your class. I recommend this least. Note that your query will still return all sub items here when you access SubItemsWithIdOne
class Item
{
private List<SubItem> SubItems { get; set; }
private List<SubItem> SubItemsWithIdOne
{
get
{
return this.SubItems.Where(sub => sub.ID == 1);
}
}
}
Option 4: Add a property on SubItem
that references it's parent Item
. Then return a list of SubItem
. This way you'll have both SubItems
and Items
where your criteria is satisfied.
...If you're working with an IEnumerable
you can do:
IEnumerable items = items
.Select(item =>
{
item.SubItems.Where(sub => sub.ID = 1);
return item;
}
);
If you want to filter children down to where there's only one child per parent, you need to start with children, select their parents, and do not touch the parents' subitems:
IQueryable<SubItem> childItems = context
.SubItems.Include("Item")
.Where(si => si.Id == 1 && si.Item.SomeAttr == someValue);
// ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// | |
// | Set a condition on the parent
// Set a condition on the child
I assume that each sub-item has a link "pointing" back at its parent.
items.Where(i => i.SubItems.Any(subItem => subItem.Id == 1));