SearchExtensions : String Search

Library of IQueryable and IEnumerable extension methods to perform searching

This project is avaliable for download as a nuget package

PM> Install-Package NinjaNye.SearchExtensions

String Searching

String searching allows you to search any number of string properties. Searches can be made against any number of search terms or against any number of string properties.

Methods

The following methods are available to both IQueryable data and IEnumerable data. Each method will return records where any of the supplied properties are matched by the rules below:

IEnumerable data collections have the following additional methods available use:

Search for a single search term within a single property

var result = queryableData.Search(x => x.Property1)
                          .Containing("searchTerm");

Search for a single search term within multiple properties

var result = queryableData.Search(x => x.Property1,
                                  x => x.Property2,
                                  x => x.Property3)
                          .Containing("searchTerm");

Search for multiple search terms within a single property

var result = queryableData.Search(x => x.Property1)
                          .Containing("search", "term");

Search for multiple search terms within multiple properties

var result = queryableData.Search(x => x.Property1,
                                  x => x.Property2,
                                  x => x.Property3)
                          .Containing("searchTerm1",
                                      "searchTerm2",
                                      "searchTerm3");

Performing Containing AND searches

Search where a single property contains a single search term
AND contains another single search term

var result = queryableData.Search(x => x.Property1)
                          .Containing("searchTerm1")
                          .Containing("searchTerm2");

Search where a single search term exists within in Property1 OR Property2
AND single search term exists within in Property3 OR Property4

var result = queryableData.Search(x => x.Property1, x => x.Property2)
                          .Containing("searchTerm")
                          .Search(x => x.Property3, x => x.Property4)
                          .Containing("searchTerm");

Search where a single search term exists in Property1 OR Property2
AND any of the multiple search terms exist within a Property3

var result = queryableData.Search(x => x.Property1, x => x.Property2)
                          .Containing("searchTerm")
                          .Search(x => x.Property3)
                          .Containing("another", "term");

Search where all search terms are within a single property

var result = queryableData.Search(x => x.Property1)
                          .ContainingAll("search", "term");

Search where all search terms exist across multiple properties

var result = queryableData.Search(x => x.Property1,
                                  x => x.Property2,
                                  x => x.Property3)
                          .ContainingAll("search", "term");

Search where a single property equals a single search term

var result = queryableData.Search(x => x.Property1)
                          .EqualTo("searchTerm");

Search where any one of multiple properties is equal to a single search term

var result = queryableData.Search(x => x.Property1,
                                  x => x.Property2,
                                  x => x.Property3)
                          .EqualTo("searchTerm");

Search where a single property is equal to any one of multiple search terms

var result = queryableData.Search(x => x.Property1)
                          .EqualTo("search", "term");

Search where any one of multiple properties is equal to any one of multiple search terms

var result = queryableData.Search(x => x.Property1,
                                  x => x.Property2,
                                  x => x.Property3)
                          .EqualTo("searchTerm1",
                                   "searchTerm2",
                                   "searchTerm3");

Search where a single property starts with a single search term

var result = queryableData.Search(x => x.Property1)
                          .StartsWith("searchTerm");

Search where any one of multiple properties starts with to a single search term

var result = queryableData.Search(x => x.Property1,
                                  x => x.Property2,
                                  x => x.Property3)
                          .StartsWith("searchTerm");

Search where a single property starts with any one of multiple search terms

var result = queryableData.Search(x => x.Property1)
                          .StartsWith("search", "term");

Search where any one of multiple properties starts with any one of multiple search terms

var result = queryableData.Search(x => x.Property1,
                                  x => x.Property2,
                                  x => x.Property3)
                          .StartsWith("searchTerm1",
                                      "searchTerm2",
                                      "searchTerm3");

Search where a single property ends with a single search term

var result = queryableData.Search(x => x.Property1)
                  .EndsWith("searchTerm");

Search where any one of multiple properties ends with a single search term

var result = queryableData.Search(x => x.Property1, x => x.Property2,
                                  x => x.Property3)
                  .EndsWith("searchTerm");

Search where a single property ends with any one of multiple search terms

var result = queryableData.Search(x => x.Property1)
                  .EndsWith("search", "term", "test");

Search where any one of multiple properties starts with any one of multiple search terms

var result = queryableData.Search(x => x.Property1, x => x.Property2,
                                  x => x.Property3)
                  .EndsWith("searchTerm1", "searchTerm2", "searchTerm3");

As well as returning the matched items, a Ranked Search also returns a hit count for each item in the form of an IRanked result. This enables you to order by hit count to retrieve the most relevant search results.

IRanked<T> result

An IRanked result is simply defined as follows:

public interface IRanked<out T>
{
int Hits { get; }
T Item { get; }
}

This is returned using the ToRanked() method:

RankedSearch for a single search term within a single property

var result = queryableData.Search(x => x.Property1)
                  .Containing("searchTerm")
                  .ToRanked();

RankedSearch for a single search term within multiple properties

var result = queryableData.Search(x => x.Property1,
                          x => x.Property2,
                          x => x.Property3)
                  .Containing("searchTerm")
                  .ToRanked();

RankedSearch for multiple search terms within a single property

var result = queryableData.Search(x => x.Property1)
                  .Containing("searchTerm1",
                              "searchTerm2",
                              "searchTerm3")
                  .ToRanked();

RankedSearch for multiple search terms within multiple properties

var result = queryableData.Search(x => x.Property1, x => x.Property2)
                  .Containing("searchTerm1",
                              "searchTerm2",
                              "searchTerm3")
                  .ToRanked();

Retrieve most relevant search results

Using ranked search you can now easily order your search results by the most relevant. This following example assumes we have a list of User which has FirstName, LastName and MiddleName string properties. In this example we want to match on those with "John" in their name and retrieve the top 10 results.

var result = context.Users.Search(x => x.FirstName,
                          x => x.LastName,
                          x => x.MiddleName)
                  .Containing("John")
                  .ToRanked()
                  // Order by Hits property of IRanked<User>
                  .OrderByDescending(r => r.Hits)
                  .Take(10);

Mixing it up

We can also mix it up with the other fluent API methods

var result = context.Users.Search(x => x.FirstName,
                          x => x.LastName,
                          x => x.MiddleName)
                  .StartsWith("john")
                  .Containing("smith")
                  .ToRanked()
                  // Order by Hits property of IRanked<User>
                  .OrderByDescending(r => r.Hits)
                  .Take(10);

ToRanked() method uses the search terms of the Containing() method combined with the properties to search to build its hit count.

Performing IEnumerable searches

The above methods can all be performed on both IQueryable collections and IEnumerable collections.

The important thing to remember when performing an in memory search is to set the culture to the type of string comparison you wish to perform. If SetCulture is not specified, StringComparison.CurrentCulture is used.

var result = enumerableData.Search(x => x.Property1)
                           // Set culture for comparison
                           .SetCulture(StringComparison.OrdinalIgnoreCase)
                           .StartsWith("abc")
                           .EndsWith("xyz")
                           .Containing("mno");

It is also possible to switch the StringComparison culture context multiple times

var result = enumerableData.Search(x => x.Property1)
                           .SetCulture(StringComparison.OrdinalIgnoreCase)
                           .StartsWith("abc")  // Uses OrdinalIgnoreCase
                           .SetCulture(StringComparison.Ordinal)
                           .EndsWith("xyz")    // Uses Ordinal
                           .SetCulture(StringComparison.CurrentCulture)
                           .Containing("mno"); // Uses CurrentCulture

Combining instructions

With SearchExtensions you can also combine search actions. For instance:

Search where a single property starts with a single search term AND containing a single search term

var result = queryableData.Search(x => x.Property1)
                  .StartsWith("abc")
                  .Containing("mno");

The ability to pass multiple search terms to any of the action methods still remains

var result = queryableData.Search(x => x.Property1, x => x.Property2)
                  .StartsWith("abc", "ninja")
                  .Containing("xyz", "extensions")

Note: Combining IEnumerable methods on IQueryable data will cause the data to be brought in to memory at the point at which the first enumerable action is taken.

If you have any new feature requests, questions, or comments, please get in touch, either, via my website, through twitter: @ninjanye or by creating an issue through the projects github page .