Search Extensions - Search within child collections now supported in v1.7
Search child collections
I received a request from a user who wondered if SearchExtensions could do something like the functionality described as follows:
Given I have a collection of Shops
I want to return all Shops that stock a Product with a Name containing "foot"
This feature is now available in the latest release via a new method,
.SearchChildren()
. Currently this is only available for LinqToObjects, not
LinqToEntities (yet):
var shops = allShops.SearchChildren(s => s.Products)
.With(p => p.Name, p => p.Desc)
.Containing("foot", "feet");
The above returns shops that contain any product that has the term "foot" or "feet" in it's Name
or Description
fields.
Support has also been added to allow searching child collections for matches on other types. The following example returns shops who have products with a price greater than 10,000:
var shops = allShops.SearchChildren(s => s.Products)
.With(p => p.Price)
.GreaterThan(10000m);
Both the above examples will return a collection of Shops
where any of
it's products match the given criteria.
The equivalent of the first example in LinqToObjects would be something like:
var shops = allShops.Where(s => s.Products.Any(p => p.Name.Contains("foot")
|| p.Name.Contains("feet")
|| p.Desc.Contains("foot")
|| p.Desc.Contains("feet")))
Additional comparisons available
Other string comparisons available for normal searching are also available for searching children, such as:
.StartsWith()
- property starts with any of the given terms.EndsWith()
- property ends with any of the given terms.EqualTo()
- property is equal to any of the given terms.Containing()
- property contains any of the given terms.ContainingAll()
- property contains all of the given terms
Non string comparisons have also been implemented when searching child collections:
.GreaterThan()
.GreaterThanOrEqualTo()
.LessThan()
.LessThanOrEqualTo()
Searching across multiple children
The ability to search across multiple children (of the same type) is also supported. Consider the following (contrived) example:
I want to return all `parents` who have a `Son` OR a `Daughter` with a `Forename` or `Nickname` beginning with 'A', 'E', 'I', 'O' or 'U'
The Model
public class Parent
{
public IEnumerable<Child> Sons { get; private set; }
public IEnumerable<Child> Daughters { get; private set; }
}
public class Child
{
public int Age {get; private set;}
public string Forename {get; private set;}
public string Nickname {get; private set;}
public string Surname {get; private set;}
}
Performing the search
Given a list of parents I could now do the following:
var result = parents.SearchChildren(p => p.Sons, p => p.Daughters)
.With(c => Forname, c => c.NickName)
.StartsWith("A", "E", "I", "O", "U");
The result
The result of this search is a collection of parents who have a son or a daughter with a forename or surname beginning with a vowel.
Note that all of a parents children will still be present for each parent.
The filter is performed against the parent but the conditions are against the children.
Thanks to @bzbetty for getting in touch via a github issues.
This feature was not something I would have completed without his request as I had not seen or envisaged a need for it.
If you have a new feature request, please get in touch by adding a comment below, contact me on twitter (@ninjanye) or you can raise an issue on the github project page
To install SearchExtensions, you can either install it via the Nuget Package manager or by using the following in the package manager console
<p class="nuget-badge"><code>PM> Install-Package NinjaNye.SearchExtensions</code></p>Let's say you have a Person object with FirstName, LastName and PhoneNumbers where PhoneNumbers is a list of PhoneNumber objects. Is it possible to search FirstName, LastName and the list of phone numbers? I see examples of SearchChildren, but not when combined with searching top-level properties.
Hi Jeff
Thanks for getting in touch. This is indeed possible and, as you mention, not something I have documented very well. Because each search is IQueryable
you can effectively chain the queries together since the result is not evaluated until you ask for the data.
To use your example, you could search for all people with a first name or last name beginning with "J" and an area code containing "123" as follows:
data.Search(p => p.FirstName, p => p.LastName)
.StartsWith("J")
.SearchChildren(p => p.PhoneNumbers)
.With(n => n.AreaCode)
.Containing("123")
Behind the scenes this simply build up an expression tree to represent the entire query so only the records you want are returned from your data store.
Hope that helps. I will try and follow up with a blog post
Cheers John