17 votes

LINQ To Examine

What is Linq To Examine?

This project allows you to query the Lucene indexes using LINQ based on your own classes. This project comes with a basic Result class which allows you to use this straight out of the box.

Watch the demo here: http://uhangout.co.uk/videos/ep077-with-craig-noble-linq-to-examine/

Code example

Lets start with some code examples. Say, I want to get all words that contains "umbraco" and it has to be a text page. This is how the code would look:

@using Umbraco.Examine.Linq
@using Umbraco.Examine.Linq.Extensions

var index = new Index<Result>();
IEnumberable<Result> results = index.Where(c => c.Name.Contains("umbraco") && c.NodeTypeAlias == "textpage").ToList();

Or alternatively,

@using Umbraco.Examine.Linq
@using Umbraco.Examine.Linq.Extensions

var index = new Index<Result>();
IEnumberable<Result> results = (from r in index
                where r.Name.Contains("umbraco") && r.NodeTypeAlias == "textpage"                select r).ToList();

Above, we are creating a new index, setting the type we wish to query. See Index constructor section for more information on how you can change the target of your search. By default, the "ExternalSearcher" is used. Anyway, then we perform the search using Where, and ToList() at the end that executes the query.


You can also perform multiple where clauses, skip and take as of v1.1

@using Umbraco.Examine.Linq
@using Umbraco.Examine.Linq.Extensions

IEnumberable<Result> results = new Index<BlogPost>()
 .Where(r.Name.Contains(searchTerm));
if(maxDate != DateTime.MinValue)
{
    results = results.Where(r => r.CreatedDate < maxDate);
}

results = results.Skip(startIndex).Take(amount).ToList();

Try the example app now!

I have created an example Umbraco web application that uses LinqToExamine.  You can find it here.  The credentials for the CMS are in the readme file.

https://github.com/base33/LinqToExamine-Demo

Installation

Install via Nuget using the command below. Then add the config manually in step 2.

Step 1:

PM> Install-Package LINQToExamine

Step 2 (I will automate this as part of step 1 asap):

Instert in > Configuration > Compilation > Assemblies

<add assembly="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

<add assembly="System.Linq.Expressions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

Index Constructor - changing the default

ConstructorDescription
Index(string searcherName") Set which Umbraco Searcher to use (see UmbracoSettings.config)
Index(ISearcher searcher) Allows you to set up and target your own Lucene index. The query is passed to you to execute. All you need to do is implement the ISearcher interface.

How to use your own custom classes?

using Umbraco.Examine.Linq.Attributes

[NodeTypeAlias("BlogPost")] //completely optional!!!
public class BlogPost
{
    [Field("nodeName")]
    public string Name { get; set; }
    [Field("content")]
    public string Content { get; set; }
    [Field("summary")]
    public string Summary { get; set; }
    [Field("blogDate")]
    public DateTime BlogDate { get; set; }
    [Field("createDate")]
    public DateTime CreateDate { get; set; }

    public Examine.SearchResult Result { get; set; } //will automatically set the result from Examine
}

There are two attributes being applied. The NodeTypeAlias attribute will automatically add the filter to the search, so you will only see results of that type. The Field attribute declares that name of the field in the index, if nothing is specified, it will default to the property name. The data will be mapped directly from the Examine SearchResult into your custom class. If you specify a property with the type Examine.SearchResult, it will automatically assign the result to your class, if you wanted access to scores etc.

So, say you wanted to find all BlogPosts' where the summary contains the terms "fishing" or "cod". The query will look like this:

@using Umbraco.Examine.Linq
@using Umbraco.Examine.Linq.Extensions

var index = new Index<BlogPost>();
IEnumerable<BlogPost> results = index.Where(c => c.Name.ContainsAny("fishing", "cod")).ToList();

Expression logic

string logic:

Extension method / operatorDescriptionExample
== (operator) Whether the field contains the value r => r.Name == "foo"
!= (operator) Whether the field does not contains the value r => r.Name != "foo"
Contains(term) Whether the field contains the value r => r.Name.Contains("foo")
Contains(term, fuzzy) Whether the field contains the value with Fuzzy enabled r => r.Name.Contains("foo", 0.8)
ContainsAny(term) Whether the field contains any of the values r => r.Name.ContainsAny("foo", "bar", "etc")
ContainsAll(term) Whether the field contains all of the values r => r.Name.ContainsAll("foo", "bar", "etc")

All these support NOT (!)

bool logic:

Extension method / operatorDescriptionExample
== (operator) Whether the field is true or false r => r.UmbracoNaviHide == false
!= (operator) Whether the field is not true or false r => r.UmbracoNaviHide != false

int and double logic:

operatorDescriptionExample
== (operator) Whether the field is equal r => r.Rating == 4
!= (operator) Whether the field is not equal r => r.Rating != 4
< (operator) Whether the field is less than r => r.Rating < 4
<= (operator) Whether the field is less than or equal to r => r.Rating <= 4
> (operator) Whether the field is greater than r => r.Rating > 4
>= (operator) Whether the field is greater than or equal to r => r.Rating >= 4

DateTime logic:

operatorDescriptionExample
== (operator) Whether the field is equal r => r.CreatedDate == date
!= (operator) Whether the field is not equal r => r.CreatedDate != date
< (operator) Whether the field is less than r => r.CreatedDate < date
<= (operator) Whether the field is less than or equal to r => r.CreatedDate <= date
> (operator) Whether the field is greater than r => r.CreatedDate > date
>= (operator) Whether the field is greater than or equal to r => r.CreatedDate >= date

More logic extension methods on the way, to help with ranges.

Fuzzy

Fuzzy allows you to match words in searches that could be "similar" to your search term but may not match your search term exactly. For example, you may be accepting a search term from the user but they may have slightly mispelt the word. Using fuzzy you can help them along their way by doing a fuzzy search that may return results that they user was searching for.

Example: A user types in "umpraco" instead of "umbraco", we will do a search that will match "umbraco" and will return results that contain "umbraco" in the name

string term = GetTerm(); //get the search term - your implementation
var index = new Index<BlogPost>();
IEnumerable<BlogPost> results = index.Where(c => c.Name.Contains("umpraco").Fuzzy(0.7)).ToList();

Boosting

Boosting allows you to boosts results that meet a specific condition. For example, you may want to boost the score for results where the content contains the word "Umbraco". Alternatively, you may want to boost results where the content contains the word "Umbraco" or summary contains "Events". Lets look at implementing both:

First Query: Get results where the name contains the term, and boost where content contains the word "Umbraco"

string term = GetTerm(); //get the search term - your implementation
var index = new Index<BlogPost>();
IEnumerable<BlogPost> results = index.Where(c => c.Name.Contains(term) 
                                              && c.Content.Contains("Umbraco").Boost(10)
                                            ).ToList();

Second Query: Get results where the name contains the term, and boost where the content contains the word "Umbraco" or the summary contains "Event":

string term = GetTerm(); //get the search term - your implementation
var index = new Index<BlogPost>();
IEnumerable<BlogPost> results = index.Where(c => c.Name.Contains(term) 
                                  && (c.Content.Contains("Umbraco") || c.Summary.Contains("event")).Boost(10)
                                ).ToList();

Wildcard Searches

Wildcard searches are easy to do. Just specify the * in the text you are querying.

For example:

@using Umbraco.Examine.Linq
@using Umbraco.Examine.Linq.Extensions

var index = new Index<Result>();
IEnumberable<Result> results = index.Where(c => c.Name.Contains("umbr*") && c.NodeTypeAlias == "textpage").ToList();

Screenshots

Package owner

Craig Noble

Craig Noble

Craig has 584 karma points

Package Compatibility

This package is compatible with the following versions as reported by community members who have downloaded this package:
Untested or doesn't work on Umbraco Cloud
Version 8.18.x (untested)

You must login before you can report on package compatibility.

Previously reported to work on versions: 7.1.x

Package Information

  • Package owner: Craig Noble
  • Created: 01/06/2015
  • Current version 1.2
  • .NET version 4.0
  • License MIT
  • Downloads on Our: 2.1K