I am fairly new to Linq and I am starting to try and filter lists and IEnumerable content without using foreach loops. In this case I want to retreive a nested property if it meets a given criteria.
Here is what I have so far:
IEnumerable<GroupTourData> test =
web_service_item.TourInstances
.Where(x => x.Availability.Where(a => a.Price > 0 && a.Grade == string.Empty)
.Count() > 0);
What I want to achieve here is an IEnumerable list of all of the prices in the matching criteria. I though this would be obtainable by adding .SelectMany(a.Price) to the end of this statement but that seems to error out in Visual Studio.
Any help or pointers would be greatly appreciated.
ok seems as this solves the issue:
if you just want the prices? then you just had your SelectMany in the wrong position:
web_service_item
.TourInstances
.SelectMany(ti => ti.Availability)
.Where(a => a.Price > 0 && a.Grade == string.Empty)
.Select(a => a.Price);
Depending on your data schema and tables... Have you tried including your sub-entities using Include?
IEnumerable<GroupTourData> test = web_service_item
.TourInstances
.Include("Availability")
.Where(x => x
.Availability
.Any(a => a.Price > 0 && a.Grade == string.Empty)
);
Related
I'm currently using a linq statement to move items from one list to another based of a condition, but i need to add a second condition and i think i'm having a syntax issue
What I'm currently doing:
var selected = Generatorlist.CallDataGeneratorsDone
.Where(item => item.SkillNumber == SkillNumber)
.ToList();
Generatorlist.CallDataGeneratorsDone = Generatorlist.CallDataGeneratorsDone
.Except(selected)
.ToList();
Generatorlist.CallDataGeneratorsNotDone.AddRange(selected);
What I'm trying to do:
var selected = Generatorlist.CallDataGeneratorsDone
.Where((item => item.SkillNumber) & (item => item.CallServer))
.ToList();
Generatorlist.CallDataGeneratorsDone = Generatorlist.CallDataGeneratorsDone
.Except(selected)
.ToList();
Generatorlist.CallDataGeneratorsNotDone.AddRange(selected);
I'm having an issue because you can't us & in a Lambda expression. I'm looking for some assistance in figuring out a way to accomplish this.
You just need to remove the second declaration of item => (it only needs to be defined once):
var selected = Generatorlist.CallDataGeneratorsDone
.Where(item => item.SkillNumber && item.CallServer)
.ToList();
You can chain Where calls together like so...
var selected = Generatorlist.CallDataGeneratorsDone
.Where(item => item.SkillNumber)
.Where(item => item.CallServer)
.ToList();
If I don't miss something, it should be && instead of & in the same expression like this:
var selected = Generatorlist.CallDataGeneratorsDone
.Where(item => item.SkillNumber && item.CallServer)
.ToList();
This is assuming that both SkillNumber and item.CallServer are of Boolean. Otherwise you will need to write acomplete predicate like item.SkillNumber = 'blah blah'.
The solution in your case would be:
Generatorlist.CallDataGeneratorsNotDone
.AddRange(Generatorlist.CallDataGeneratorsDone
.Except(Generatorlist.CallDataGeneratorsDone
.Where(item => item.SkillNumber && item.CallServer))
.ToList())
Query 1
var resulty = db.QIS
.Where(w=>w.QSA.Any(a => a.QSID != w.QSID))
.Select(s => s.QSID).ToList();
Query 2
var resultz = db.QIS
.Where(w=>w.QSA.Where(h => h.QSID == w.QSID).Select(s => s.QSID).FirstOrDefault() != w.QSID)
.Select(s => s.QSID).ToList();
Table QIS and QSA are related Many:Many. The idea here is to find how many QIS.ID's are not Found in QSA where QIS.QID == QSA.QID.
Query 1 returns 0
Query 2 on the other hand gives me what I expected to see ( the list off all non matching QIS records.)
Why will the any not work? - i found myself running into the same situation a couple of times now in seperate scenarios... thanks for any help / thoughts.
PS: Prefer method syntax.
In the filtering in your second version, will only be true if the inner Where clause returns no elements, so that FirstOrDefault() returns null or 0 (depending on if the type is nullable or not).
w=>w.QSA.Where(h => h.QSID == w.QSID)
.Select(s => s.QSID).FirstOrDefault() != w.QSID
Which is equivalent to (now assuming QSID is a non nullable numeric type, if it is nullable, use null instead of zero):
w=>w.QSA.Where(h => h.QSID == w.QSID)
.Select(s => s.QSID).FirstOrDefault() == 0
which can be rewritten to:
w=>w.QSA.Where(h => h.QSID == w.QSID).FirstOrDefault() == null
which can be rewritten to:
w=>!w.QSA.Any(h => h.QSID == w.QSID)
which is nearly the same as your initial version, but not exactly. You still want to check for equivalence inside the Any() filter, but then negate the result.
I have a List<Locations> that will be filtered to yield a set of results relevant to a search term.
At the moment, I tried these 'search results' by filtering with the following:
return locations.Where(o => o.Name.Contains(term)).Take(10).ToList();
Problem
If I were to enter 'Chester' as the search term, I will never see the item "Chester" despite it existing in the locations list. The reason for this is that there are 10 or more other items in the list that contain the String "Chester" in their name (Manchester, Dorchester etc.).
How can I use LINQ to first of all take the results that start with the search term?
What I've Got So Far
var startsWithList = list.Where(o => o.Name.StartsWith(term)).Take(10).ToList();
var containsList = list.Where(o => o.Name.StartsWith(term) && !startsWithList.Contains(o)).Take(10 - startsWithList.Count).ToList();
return startsWithList.AddRange(containsList);
I don't like the above code at all. I feel like this should be achieved in one Where as opposed to performing two Where and Take's and combining the two lists.
just order ascending before Take, putting a lower value for items starting with term.
return locations.Where(o => o.Name.Contains(term))
.OrderBy(m => m.Name.StartsWith(term) ? 0 : 1)
//or OrderByDescending(m => m.Name.StartsWith(term))
.Take(10)
.ToList();
adapted with the improvement of MikeT (exact match before StartsWith), you could just do
return locations.Where(o => o.Name.Contains(term))
.OrderBy(m => m.Name.StartsWith(term)
? (m.Name == term ? 0 : 1)
: 2)
.Take(10)
.ToList();
I have created a new github project that uses expression trees to search for text in any number of properties
It also has a RankedSearch() method which returns the matching items with the number of hits for each record meaning you can do the following:
return locations.RankedSearch(term, l => l.Name)
.OrderByDescending(x => x.Hits)
.Take(10)
.ToList();
If you wanted you could search accross multiple properties
return locations.RankedSearch(term, l => l.Name, l => l.City)
... or for multiple terms,
return locations.RankedSearch(l => l.Name, term, "AnotherTerm" )
... or for both multiple properties and multiple terms
return locations.RankedSearch(new[]{term, "AnotherTerm"},
l => l.Name,
l => l.City)
Checkout this post for more information on the SQL generated and other usages:
http://jnye.co/Posts/27/searchextensions-multiple-property-search-support-with-ranking-in-c-sharp
You can download this as a nuget package to:
https://www.nuget.org/packages/NinjaNye.SearchExtensions/
Raphaƫl's Solution will work but if you were say searching for Warwick you could find that it might not put Warwick the top of the list if Warwickshire is also a possible location,using the scores you can also extend this infinitely with more matching methods, as well as tweaking the score values to refine your search order
return locations.Select(l => New {SearchResult=l,
Score=(L.Name == Term ?
100 :
l.Name.StartsWith(term) ?
10 :
l.Name.Contains(term) ?
1 :
0
)})
.OrderByDescending(r=>r.Score)
.Take(10)
.Select(r => r.SearchResult);
note i would probably do this by making a Match method and do the logic in there rather than in the linq like i did above so it would just be
return locations.OrderByDescending(Match).Take(10);
All Solutions will work but the better score can gain more easier as below
return locations.Where(o => o.Name.Contains(term))
.OrderBy(m => m.Name.IndexOf(term))
.Take(10)
.ToList();
as a result each name that contain term at nearest to start, show first.
What about this?
return locations.Where(o => o.Name.Contains(term))
.OrderBy(m => m.Length)
.Take(10)
.ToList();
I am using nHibernate to retrieve a collection of orders (and its order lines) from a Sql Server database.
This is my ShipmentOrder class:
public class ShipmentOrder
{
private ICollection<ShipmentDetail> _ShipmentsDetails;
public virtual ReadOnlyCollection<ShipmentDetail> ShipmentsDetails
{
get { return (new List<ShipmentDetail>(_ShipmentsDetails).AsReadOnly()); }
}
}
nHibernate returns a IList with all the details (ShipmentsDetails) loaded (since I Eager load them).
Now, I would like to filter my collection of ShipmentOrder and ShipmentDetail and get back a collection of ShipmentDetail.
I've tried something like this:
IList<ShipmentOrder> Results;
// Fetch orders using nHibernate
Results = FetchOrders();
var shipmentLines = Results
.Where(x => x.Company == "XXX" && x.OrderNumber == "111")
.SelectMany(x => x.ShipmentsDetails)
.Where(s => s.RowNumber == 1 && s.RowSeq == 0)
.ToList();
But I've realized that I obtain multiple results of the same line.
I've converted the lamda expression like this:
var shipmentLines = Results
.Where(x => x.Company == "XXX" && x.OrderNumber == "111")
.SelectMany(x => x.ShipmentsDetails)
.Where(s => s.RowNumber == 1 && s.RowSeq == 0)
.Distinct()
.ToList();
and it works fine.
I was wondering if there's a better way to achieve the same result without the distinct.
UPDATE:
I am using SelectMany here cause this is the only way I've found to apply filters to children (ShipmentsDetails).
this looks like Linq2Objects, therefor the IList Results contains duplicate records, probably you do eager fetching to levels deep which unfortunatly results in duplicate root entities. use distinctrootentity-resulttransformer in the query
I am trying to figure out why my delegate function does not work, any help would be appreciated, this is probably a small issue but I have been working on it for a while and connot figure it out, my code:
//remove all matching people from this list
public void RemovePeopleFromLookup(Predicate<PeopleDTO> _people)
{
//Lookup is an internal readonly ICollection of PeopleDTO
Lookup.RemoveAll(_people);
}
//call the method as below:
//data is a collection of PeopleDTO
mylookupobj.RemovePeopleFromLookup(x => data.Any(y => y.Name == x.Name && x.Type == FieldElement.Strange));
For some reason all people get removed from this lookup, this is not correct, I only want to remove the people who are
Strange
who DONOT exist in the data collection
EDIT:
The data collection can be an object of different types -> strange, noisy etc...
The mylookupobj.Lookup data collection is similar to the data collection and contain contain multiple types hence why I wrote my query that way
EDIT2: I missed out this information which may be very important...
public class PersonDTO
{
//Name
//Type
//Age
//Desc
}
Inside the mylookupobj.Lookup - all the properties contain data, however inside the data collection only the Name + Type is present.
A simpler and more efficient predicate would be:
x => (x.Type == FieldElement.Strange) && data.Any(y => y.Name == x.Name)
But I admit I don't see a principal problem with either.
Edit: seems 1 of the conditions has to be inverted.
x => (x.Type == FieldElement.Strange) && ! data.Any(y => y.Name == x.Name)
The call to Any is the problem. Essentially it's running through the collection multiple times. i.e. if any of the objects in the collection matches the condition, remove the item. Try this:
mylookupobj.RemoveFieldFromLookup(y => y.Name == x.Name && x.Type == FieldElement.Strange);
Please try this.
mylookupobj.RemovePeopleFromLookup(x => data.Contains(y => y.Name == x.Name)
&& x.Type == FieldElement.Strange);