Displaying specific elements of a collection's subcollection - c#

I have a List collection that contains a List subcollection as a property within it, and I want to filter out items in that subcollection based on the value of certain properties.
To simplify, I'll call the main collection THING and the subcollection SUBTHING. They are different types. THINGS can have 1 to many SUBTHINGS. SUBTHING has 2 properties I want to filter by, PROP1 should equal 1 (it can equal 1,2,3) and PROP2 should not be NULL (it can contain a string).
So when I use a query like the one below it seems to give me what I want (though I'm not sure All() is doing what I expect):
search = from c in search
where c.SUBTHING.All(s=>s.PROP1==1)
select c;
Then I get suspicious when I add the other property:
search = from c in search
where c.SUBTHING.All(s=>s.PROP1==1 && s.PROP2 != NULL)
select c;
And I get THINGS that have PROP2 as Null.
When I switch to Any() I lose all filtering on SUBTHING and it shows SUBTHINGS where PROP1 = 1,2,3 and where PROP2 is NULL and not NULL.
What I'm trying to get is a collection that lists all THING IDs and then lists the Name of all SUBTHINGS, sort of like this:
THING.ID
SUBTHING.Name
SUBTHING.Name
THING.ID
SUBTHING.Name
SUBTHING.Name
Is this possible to also filter SUBTHINGS while filtering THINGS with LINQ since THING and SUBTHING are two different types?

Try something like this:
search =
from c in search
where c.SUBTHING.All(s=>s.PROP1==1 && s.PROP2 != NULL)
select new {
ThingId = c.ThingID,
Something = c.SomeThing.Select(x=>x.Name)
};
To apply filter on subitems try:
from product in products
where product.productid == 1
from image in product.productimages
where image.ismainimage
select image.imagename
From : 101 linq queries

One way is using Enumerable.Where and an anonymous type:
var result = from thing in search
from subthing in thing.subthings
where subthing.prop1 == 1 && subthing.prop2 != null
select new {ID = thing.ID, Name = subthing.Name};
foreach(var x in result)
{
Console.WriteLine("ID={0} Name{1}", x.ID, x.Name);
}

You need a projection as you are querying over the parent entity (THING) but in the result set you want to only have a subset of its SUBTHINGS.
You can do it e.g. in the following way:
class Thing
{
Thing(Thing original, IEnumerable<Subthing> subthings)
{
// Initialize based on original and set the collection
//
...
}
}
and then run the query like this:
var filtered = from c in search
select new Thing(c, c.Subthings.Where(x => x.PROP1 == 1 && x.PROP2 != null))

I'm not sure any of these answers really give you what you want (although they're close). From my understanding, you want a list of THINGs in which at least 1 SUBTHING has the values you're interested in (in this case, Prop1 == 1 and Prop2 != null). There are a few options here, just depends on whether you're working from a THING or a SUBTHING perspective.
Option 1: THING approach.
You're looking at any THING that has a SUBTHING with your condition. So:
var result = from thing in search
where thing.Subthings.Any(tr => tr.Prop1 == 1 && tr.Prop2 != null)
select new { ID = thing.ID, Names = thing.Subthings.Where(tr => tr.Prop1 == 1 && tr.Prop2 != null) };
Option 2: SUBTHING approach.
You're looking at ALL SUBTHINGs and finding the ones where the condition is met, grouping by the ID at that point.
var result = from thing in search
from sub in thing.Subthings
where sub.Prop1 == 1 && sub.Prop2 != null
group sub by thing.id into sg
select new { ID = sg.Key, Names = sg.Select(tr => tr.Name) };
I like this approach just a little better, but still room for improvement. The reason I like this is because you find the SUBTHINGs first, and only then will it pull the THING that's associated with it (instead of first having to find if any SUBTHING matches the criteria, and THEN selecting it).
Option 3: Hybrid approach.
This is a little of both. We're going to select from SUBTHINGs either way, so might as well just perform the select. Then, if any of the projected subcollections have any elements, then we return our THING with the Names.
var result = from thing in search
let names = thing.Subthings
.Where(sub => sub.Prop1 == 1 && sub.Prop2 != null)
.Select(sub => sub.Name)
where names.Any()
select new { ID = thing.ID, Names = names };
Cleanest option, in my opinion. The Any extension method on a collection without any parameters will return true if there are any items in the collection. Perfect for our situation.
Hope that helps, let us know what you came up with.

Related

ASP.NET MVC C# Select and Where Statements

I'm having trouble understanding .Select and .Where statements. What I want to do is select a specific column with "where" criteria based on another column.
For example, what I have is this:
var engineers = db.engineers;
var managers = db.ManagersToEngineers;
List<ManagerToEngineer> matchedManager = null;
Engineer matchedEngineer = null;
if (this.User.Identity.IsAuthenticated)
{
var userEmail = this.User.Identity.Name;
matchedEngineer = engineers.Where(x => x.email == userEmail).FirstOrDefault();
matchedManager = managers.Select(x => x.ManagerId).Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
}
if (matchedEngineer != null)
{
ViewBag.EngineerId = new SelectList(new List<Engineer> { matchedEngineer }, "PersonId", "FullName");
ViewBag.ManagerId = new SelectList(matchedManager, "PersonId", "FullName");
}
What I'm trying to do above is select from a table that matches Managers to Engineers and select a list of managers based on the engineer's id. This isn't working and when I go like:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
I don't get any errors but I'm not selecting the right column. In fact the moment I'm not sure what I'm selecting. Plus I get the error:
Non-static method requires a target.
if you want to to select the manager, then you need to use FirstOrDefault() as you used one line above, but if it is expected to have multiple managers returned, then you will need List<Manager>, try like:
Update:
so matchedManager is already List<T>, in the case it should be like:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
when you put Select(x=>x.ManagerId) after the Where() now it will return Collection of int not Collection of that type, and as Where() is self descriptive, it filters the collection as in sql, and Select() projects the collection on the column you specify:
List<int> managerIds = managers.Where(x => x.EngineerId == matchedEngineer.PersonId)
.Select(x=>x.ManagerId).ToList();
The easiest way to remember what the methods do is to remember that this is being translated to SQL.
A .Where() method will filter the rows returned.
A .Select() method will filter the columns returned.
However, there are a few ways to do that with the way you should have your objects set up.
First, you could get the Engineer, and access its Managers:
var engineer = context.Engineers.Find(engineerId);
return engineer.Managers;
However, that will first pull the Engineer out of the database, and then go back for all of the Managers. The other way would be to go directly through the Managers.
return context.Managers.Where(manager => manager.EngineerId == engineerId).ToList();
Although, by the look of the code in your question, you may have a cross-reference table (many to many relationship) between Managers and Engineers. In that case, my second example probably wouldn't work. In that case, I would use the first example.
You want to filter data by matching person Id and then selecting manager Id, you need to do following:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).Select(x => x.ManagerId).ToList();
In your case, you are selecting the ManagerId first and so you have list of ints, instead of managers from which you can filter data
Update:
You also need to check matchedEngineer is not null before retrieving the associated manager. This might be cause of your error
You use "Select" lambda expression to get the field you want, you use "where" to filter results

Remove child list item when checking grandchild list using Linq

The following code shows how I am assigning data into IEnumerable<UnCompletedJobDetailsBO>.
There is a list (IEnumerable<UnCompletedJobDetailsBO>) that has another list (List<JobDetailsBO>), with that child list (List<JobDetailsBO>) having a list on it. But the AllocationDetailList only ever has one list item.
public IEnumerable<UnCompletedJobDetailsBO> GetControlDetails(DateTime startDate)
{
var controlDetails =
(from booking in db.BookingDetail
where booking.BookingDateTime >= startDate
orderby booking.DocketNo
select new UnCompletedJobDetailsBO()
{
CustomerName = booking.Customer.Name,
CompanyName = booking.CompanyDetail.Name,
JobList =
(from job in db.BookingJob.Where(x => x.BookingID == booking.BookingID) //get job list
select new JobDetailsBO()
{
JobID = job.JobID,
JobType = job.JobType,
ItemName = job.ItemName,
AllocationDetailList =
(from jobAllocationDetail in db.JobAllocation
join returnUnCollected in db.JobReturnUnCollected
on jobAllocationDetail.JobAllocationDetailID
equals returnUnCollected.JobAllocationDetailID
into returnJob
from returnUnCollected in returnJob.DefaultIfEmpty()
where (jobAllocationDetail.Booking.BookingID == booking.BookingID)
select new AllocationBO()
{
JobUnCollectedID = returnJob.JobUnCollectedID,
JobType = jobAllocationDetail.JobType,
CurrentStatus = jobAllocationDetail.CurrentStatus,
}).DefaultIfEmpty().ToList(),
}).DefaultIfEmpty().ToList(),
}).ToList();
return controlDetails;
}
I want to remove the JobList item if the inner list (AllocationDetailList) item satisfies the condition below. Sometimes AllocationDetailList may be null, so I check that also. But when I write below query, it does not remove that particular JobList item that satisfies the condition. Thanks in advance.
public List<UnCompletedJobDetailsBO> RemovePODFromSelectedList(
List<UnCompletedJobDetailsBO> unCompletedJobDetailsBO)
{
unCompletedJobDetailsBO
.SelectMany(y => y.JobList)
.ToList()
.RemoveAll(x => ((x.AllocationDetailList[0] != null) ?
x.AllocationDetailList[0].JobType == "D" &&
x.AllocationDetailList[0].JobUnCollectedID == null &&
x.AllocationDetailList[0].CurrentStatus == 5 :
x.AllocationDetailList.Count > 1));
return unCompletedJobDetailsBO;
}
Without a good, minimal, complete code example, I'm not sure that any performance concern can be addressed. It's hard enough to fully understand the question as it is, but without being able to actually test the code, to reproduce and observe a specific performance concern, it's hard to know for sure where your concern specifically lies, never mind how to fix it.
That said, from the code you posted, it is clear why items are not being removed from the list. The basic issue is that while the SelectMany() method does have the effect of allowing you to enumerate all of the elements from all of the different JobList objects as a single enumeration, the elements are enumerated as a new enumeration.
When you call ToList(), you are creating a whole new list from that new enumeration, and when you call RemoveAll(), you are only removing elements from that new list, not the lists from which they originally came.
You say you can get it to work with a for loop. I assume you mean something like this:
public List<UnCompletedJobDetailsBO> RemovePODFromSelectedList(
List<UnCompletedJobDetailsBO> unCompletedJobDetailsBO)
{
foreach (var item in unCompletedJobDetailsBO)
{
item.JobList.RemoveAll(x => ((x.AllocationDetailList[0] != null) ?
x.AllocationDetailList[0].JobType == "D" &&
x.AllocationDetailList[0].JobUnCollectedID == null &&
x.AllocationDetailList[0].CurrentStatus == 5 :
x.AllocationDetailList.Count > 1));
}
return unCompletedJobDetailsBO;
}
Note: there is no need to return unCompletedJobDetailsBO. That entire object is unchanged, never mind the variable. The only thing the code is modifying is each individual JobList object within the passed-in object's members. I.e. the above method could actually have a return type of void, and the return statement could be removed entirely.
It is possible you could speed the code up by removing the elements in a different way. The List<T>.RemoveAll() method is in fact reasonably efficient, with O(n) cost. But it still involves copying all of the data in the list after the first element that is removed (so that all the elements are shifted down in the list). If you have to have the list ordered, this may be as good as you can do, but if not, you could process the removal differently, or use a different data structure altogether, something unordered where removal of one or more elements costs less.
But again, without more details and without a good example to work with addressing that particular issue doesn't seem practical here.
The condition
x.AllocationDetailList[0] != null
will throw exception if there is no item in the AllocationDetailList. Instead you need to check
x.AllocationDetailList!=null && x.AllocationDetailList.Count>0.
Also .ToList() after SelectMany in your code will create a new list and items will be removed from that new list instead of unCompletedJobDetailsBO. You need to modify the remove function as below
unCompletedJobDetailsBO.ForEach(y => y.JobList.RemoveAll(x => ((x.AllocationDetailList != null && x.AllocationDetailList.Count>0)
?
x.AllocationDetailList[0].JobType == "D"
&& x.AllocationDetailList[0].JobUnCollectedID == null
&& x.AllocationDetailList[0].CurrentStatus == "5"
:
x.AllocationDetailList.Count > 1
)
));

To apply mulitple criteria in lambda expression in c#

I have two main tables Listings and Place . In listing table there is a field PlaceId which referes to a Place entity/row/object . I want to query on both tables so that i get both of them like this .
var query = context.Listings
.Include("Place")
.Where(l => l.Place.TypeId == Type.Ro)
.OrderBy(l => l.Id).ToList();
after this now i want to put some filter on this query , here is the condition .
i got only a string like this var filter = "1,2,4"; . Now i want to filter on listing to gett all these listing where bedroom is equal to 1 OR 2 OR 4 .
What i have done
string minBeds = "1,2,4";
foreach (var item in minBeds.Split(','))
{
int minBed = int.Parse(item);
query = query.Where(l=>l.Place.Bedroom == minBed).ToList();
}
But doing this is giving me Zero result.
The problem with the way you're filtering it. After the first pass, you're filtering out everything except where Bedroom == 1, on the second pass you're filtering out everything except where Bedroom == 2, but since the only items in the list have Bedroom == 1, you won't have anything in the result set.
The solution is to use the conventional C# || operator:
query = query.Where(l => l.Place.Bedroom == "1" ||
l.Place.Bedroom == "2" ||
l.Place.Bedroom == "4");
Or if you want to be more flexible, use the Contains method:
string[] minBeds = "1,2,4".Split(',');
query = query.Where(l => minBeds.Contains(l.Place.Bedroom));
Note if Bedroom is an integer, you'll need to convert the input to an appropriate type first:
var minBeds = "1,2,4".Split(',').Select(int.Parse);
query = query.Where(l => minBeds.Contains(l.Place.Bedroom));
Also note, I've eliminated the ToList here. Unless you need to access items by index and add / remove items from the result collection, it's most likely just a waste of resources. You can usually rely on Linq's native laziness to delay processing to query until you really need the result.

Linq Remove results with ALL statement

Hi I'm trying to use Linq to remove "all" entities from a list.
Problem: I'm searching for users that have certain certificates in my database. Thing is that it returns them row by row.... But what I need to check is: If the user holds all the required certificates. This should be checked against my int array.
This is my array: [3,5,16], now I want to delete all user who does not have all three of those from the list. Name of the array in code is mandatory!
The listitems I get back looks like this
listitem.CertificateValue
listitem.Uid
listitem.NameOfPerson
So basicly for this example Peter has three rows in the list, in this case all the rows needed to stay in the list. But Philip only has 2 rows and hence both of these should be deleted since he does not fullfill the total search criteria.
Also copyOfMandatoryis just to not mess with the original collection and cause an expection(collection size changed).
foreach (var item in copyOfMandatory)
{
if (!mandatoryusers.All(i => mandatory.Contains(i.CertificateValue)
|| i.Uid == item.Uid))
{
mandatoryusers.RemoveAll(i => i.Uid == item.Uid);
}
}
UPDATE
RemoveAll works like a charm it the if statement that does not work as expected.
Doing this it does not take away any part of the list, I began wiht && instead of || but whne doing that it kills everything but the last person it encounters as long as he/she fullfills the search criteria.
Anyone have a hint on how to do this?
I would try something like that
var uIdToRemove = mandatoryusers.GroupBy(m => m.Uid)
.Where(g => mandatory.Except(g.Select(s => s.CertificateValue)).Any())
.Select(g => g.Key).ToList();
mandatoryusers.RemoveAll(x => uidToRemove.Contains(x.Uid));
Your All call is not granular enough: it is trying to ensure that ALL entries exist at all times... Not that all entries PER USER exist.
Try converting each entry to a dictionary:
var dict = new Dictionary<int, List<ItemType>>();
foreach (var mandatoryItem in mandatoryItems)
{
List<ItemType> itemTypeValue = null;
if (!dict.TryGetValue(mandatoryItem.Uid, out itemTypeValue)
{
itemTypeValue = new List<ItemType>();
dict.Add(mandatoryItem.Uid, itemTypeValue);
}
itemTypeValue.Add(mandatoryItem);
}
Now you have all ItemType at the key of Uid. From here, use LINQ:
mandatoryusers = mandatoryusers.Where(i => dict[i.Uid].All(x => mandatory.Contains(x.CertificateValue));
Your if All criteria is off.
if (!mandatoryusers.All(i => mandatory.Contains(i.CertificateValue)
|| i.Uid == item.Uid))
{
mandatoryusers.RemoveAll(i => i.Uid == item.Uid);
}
It needs to be with an && not an || and you should call Any() instead of All()
if (!mandatoryusers.Any(i => mandatory.Contains(i.CertificateValue)
&& i.Uid == item.Uid))
{
mandatoryusers.RemoveAll(i => i.Uid == item.Uid);
}
Hopefully I understood what your logic and question correctly.
Your if statement isn't correct (as you stated) - it's attempting to check whether all items contain a certificate with an id in mandatory or where the userid is the current item. What you should be doing is filtering by userid first and then checking the certificates.
This isn't the way I would do it, though. I'd group the results by User and then check the certificates
var usersWithAllCertificates = mandatoryUsers.GroupBy(mu => mu.Uid)
//Select the ones that have all 3 certificates
.Where(g => g.Select(u => u.CertificateValue)
.Intersect(mandatory).Count() == 3)
.Select(g => g.ToList());
The Intersect operator will combine the lists and the result will be the items that are the same in both lists. So, if the user has all 3 certificates (3, 5 and 16) the result of the intersect will be 3 items. The usersWithAllCertificates object will include all the users you want. This is explicitely selecting the values you want instead of removing the ones you don't want, which imo is a better way of going about it. Note that this assumes each user is only in the list once (i.e. only has 3 certificates)

Possible to do this in linq?

I am wondering if something like this could be done(of course what I have written does not work but that's what I am essentially trying to achieve) .
var test = u.Owner;
a.Table.Where(u => test == true)
I have a linq query that I want to reuse(basically I got a query that I use 5 times) and the only thing that changes is what I am comparing against.
So in the above u.Owner is compared against true. In another query it would look the same but instead of u.Owner I might have u.Add == true or u.Edd == true.
So is there a way I can sort of reuse what I have. Of course in my code the query is a bit longer but I just shortened down.
Edit
Basically the whole query
List<Permission> clearence = user.PermissionLevels.Where(u => u.Id == Id &&( u.Add == permissionNeeded || u.Permission.Name == PermissionTypes.Owner)).ToList();
permissionNeeded == Enum
So my orignal way of doing it was u.Permission.Name == permissionNeeded so I compared the enum value to a string.
Now my db model has change and I need to check against 5 different permissions separately that are bools and not strings.
u.Add = true || u.Owner == true;
u.Edit = true || u.Owner == true;
u.Delete= true || u.Owner == true;
u.View= true || u.Owner == true;
Thats all changes for the entire query so that's why I am trying to make it into one query(just like I had it before).
So I am thinking of having a switch statement. The method still takes in a permissionNeeded(enum) I then go through and determine what clause I need and some how insert it into the query.
switch(PermssionNeeded)
{
case PermissionTypes.Add:
u.Add;
break;
// all other cases here.
}
Take advantage of the fact that Where can have any function taking type Table as a parameter and returning a boolean to create a function like:
public IQueryable<Table> QueryTables(Func<Table, bool> testFunction)
{
return a.Table.Where(testFunction).AsQueryable<Table>();
}
Edit: (in addition to edit to add AsQueryable above, which I earlier forgot)
If all you want to do is vary the boolean field used in the test, and you don't want to have to specify an entire function (which you're about to find out is much easier), you would need to use some reflection:
using System.Reflection;
public IQueryable<Table> QueryTables(PropertyInfo pi)
{
return a.Table.Where(t => (bool)(pi.GetGetMethod().Invoke(t, null))).AsQueryable<Table>();
}
To construct the PropertyInfo object, use something like:
PropertyInfo pi = typeof(Table).GetProperty("Owner");
I prefer the earlier method, but I did want to show that something like this is at least possible.
If you only want to specify the property you are checking you can do
public IEnumerable<Table> GetTables(Func<Table,bool> getValue)
{
return a.Table.Where(table => /*some common filter*/)
.Where(table => getValue(table))
}
var query = from u in uSrc join v in vSrc on u.ID equals v.RelatedUID
where v.Valid && u.Created < DateTime.UtcNow.AddDays(-365)
select u; // relatively complicated starting point.
var q1 = query.Where(u => u.Add); // must also have Add true
var q2 = query.Where(u => u.Test); // must also have Test true
var q3 = query.Where(u => u.ID < 50); // must also have ID < 50
And so on.
Edit:
Okay, so your starting query is:
List<Permission> clearence = student.PermissionLevels.Where(u => u.Id == Id &&( u.Add == permissionNeeded || u.Permission.Name == PermissionTypes.Owner)).ToList();
However, note that this creates a list, so any further work done on it will be a matter of Linq-to-objects. We'll come back to that in a minute, as it's sometimes good and sometimes not.
Now, if I understand you correctly, you need different sets for different cases, which you can do with your query as per:
var set0 = clearance.Where(u.Add = true || u.Owner == true);
var set1 = clearance.Where(u.Edit = true || u.Owner == true);
var set2 = clearance.Where(u.Delete= true || u.Owner == true);
var set3 = clearance.Where(u.View= true || u.Owner == true);
Now, this will work, but may not be the best approach. If we go back to the original query, we don't have to do ToList(), but can have:
IQueryable<Permission> clearence = student.PermissionLevels.Where(u => u.Id == Id &&( u.Add == permissionNeeded || u.Permission.Name == PermissionTypes.Owner));
Now, in the first case because we built a list we first got all values that matched the critera backed, and then stored it in memory, in clearance.
In the second case, clearance doesn't store any values at all, but instructions on how to get them.
The question is which is better to use. In the case where we are going to end up using the vast majority of the objects returned by the first query on its own, then there is a performance boost in using the list version, because they are loaded into memory only once, and then taken from memory without hitting the database again.
However, in most cases, it's better to do the second version for two reasons:
We hit the database in each case, but only retrieve the objects needed in that case.
We don't store anything in memory longer than necessary. Above a certain amount this is an important performance matter in itself.
The time to first item is faster this way.
We can further refine, for example if we do the following:
var trimmed = from set0 select new{u.Id, u.Permission.Name};
The we retrieve anonymous objects with Id and Name properties that are all we care about for a particular case, and not all of the relevant fields are retrieved from the database, or other source.
I've recently come to prefer a Dictionary over switch statements. You could store all your lambas in a Dictionary<PermissionNeeded, Func<User, bool>> that would look like this:
Dictionary<PermissionNeeded, Func<User, bool>> Permissions =
new Dictionary<PermissionNeeded, Func<User, bool>> {
{ PermissionNeeded.Add, u => u.Add }, // don't need to specify == true
{ PermissionNeeded.Edit, u => u.Edit },
...
etc
};
And you would call it like this:
var clearance = Permissions[PermissionNeeded.Add](user);
or maybe
var clearance = Permissions[PermissionNeeded.Add](user) && Permissions[PermissionNeeded.Edit](user);
or perhaps
var clearance = Permission[PermissionNeeded.Add](user) || Permissions[PermissionNeeded.View](user);
and so on. Even if you don't have a user object, I think this would still be valid and make the code pretty easy to read, and if you have to modify your functions, its all in the dictionary...

Categories