SolrNet Documentation

Download as pdf or txt
Download as pdf or txt
You are on page 1of 41

1.

Overview and basic usage


First, we have to map the Solr document to a class. Let's use a subset of
the default schema that comes with the Solr distribution:

public class Product {


[SolrUniqueKey("id")]
public string Id { get; set; }

[SolrField("manu_exact")]
public string Manufacturer { get; set; }

[SolrField("cat")]
public ICollection<string> Categories { get; set; }

[SolrField("price")]
public decimal Price { get; set; }

[SolrField("inStock")]
public bool InStock { get; set; }
}

It's just a POCO with some attributes: SolrField maps the attribute to a Solr field
and SolrUniqueKey (optional but recommended) maps an attribute to a Solr unique
key field.
Now we'll write some tests using this mapped class. Let's initialize the library:

[TestFixtureSetUp]
public void FixtureSetup() {
Startup.Init<Product>("http://localhost:8983/solr");
}

Let's add a document (make sure you have a running Solr instance before running
this test):
[Test]
public void Add() {
var p = new Product {
Id = "SP2514N",
Manufacturer = "Samsung Electronics Co. Ltd.",
Categories = new[] {
"electronics",
"hard drive",
},
Price = 92,
InStock = true,
};

var solr =
ServiceLocator.Current.GetInstance<ISolrOperations<Product>>();
solr.Add(p);
solr.Commit();
}

Let's see if the document is where we left it:


[Test]
public void Query() {
var solr =
ServiceLocator.Current.GetInstance<ISolrOperations<Product>>();
var results = solr.Query(new SolrQueryByField("id", "SP2514N"));
Assert.AreEqual(1, results.Count);
Console.WriteLine(results[0].Price);
}

2.

Mapping
Solr fields defined in your schema.xml must be mapped to properties in a
.NET class. There are currently three built-in ways to map fields:

Attributes (default)
With this method you decorate the properties you want to map with
the

SolrField

and

SolrUniqueKey

attributes. The attribute parameter indicates the

corresponding Solr field name.


Example:
public class Product {
[SolrUniqueKey("id")]
public string Id { get; set; }

[SolrField("manu_exact")]
public string Manufacturer { get; set; }

[SolrField("cat")] // cat is a multiValued field


public ICollection<string> Categories { get; set; }

[SolrField("price")]
public decimal Price { get; set; }

[SolrField("inStock")]
public bool InStock { get; set; }

[SolrField("timestamp")]
public DateTime Timestamp { get; set; }

[SolrField("weight")]
public double? Weight { get; set;} // nullable property, it might not be
defined on all documents.
}

This way of mapping is implemented by the

AttributesMappingManager

class.

Index-time field boosting


You can also use the mapping attribute to apply a boost to a specific field at indextime.
[SolrField("inStock", Boost = 10.5)]
public bool InStock { get; set; }

.. this will add a boost of 10.5 to the InStock field each time the document is
indexed.

All-properties
This maps each property of the class to a field of the exact same name as the
property (note that Solr field names are case-sensitive). It's implemented by
the

AllPropertiesMappingManager

class. Note that unique keys cannot be inferred,

and therefore have to be explicitly mapped. The same mapping as above could be
accomplished like this:
public class Product {
public string id { get; set; }
public string manu_exact { get; set; }
public ICollection<string> cat { get; set; }
public decimal price { get; set; }
public bool inStock { get; set; }
public DateTime timestamp { get; set; }
public double? weight { get; set; }
}

Then to add the unique key:


var mapper = new AllPropertiesMappingManager();
mapper.SetUniqueKey(typeof(Product).GetProperty("id"));

Manual mapping
This allows you to programmatically define the field for each property:
public class Product {

public string Id { get; set; }


public string Manufacturer { get; set; }
public ICollection<string> Categories { get; set; }
public decimal Price { get; set; }
public bool InStock { get; set; }
public DateTime Timestamp { get; set; }
public double? Weight { get; set; }
}
var mgr = new MappingManager();
var property = typeof (Product).GetProperty("Id");
mgr.Add(property, "id");
mgr.SetUniqueKey(property);
mgr.Add(typeof(Product).GetProperty("Manufacturer"), "manu_exact");
mgr.Add(typeof(Product).GetProperty("Categories"), "cat_exact");
mgr.Add(typeof(Product).GetProperty("Price"), "price");
mgr.Add(typeof(Product).GetProperty("InStock"), "inStock");
mgr.Add(typeof(Product).GetProperty("Timestamp"), "timestamp");
mgr.Add(typeof(Product).GetProperty("Weight"), "weight");

Dictionary mappings and dynamic fields


Solr dynamicFields can be mapped differently depending on the use case. They
can be mapped "statically", e.g, given:
<dynamicField name="price_*"
stored="true"/>

type="integer"

indexed="true"

a particular dynamicField instance can be mapped as:

[SolrField("price_i")]
public decimal? Price {get;set;}

However, it's often necessary to have more flexibility. You can also map
dynamicFields as a dictionary, with a field name prefix:
[SolrField("price_")]
public IDictionary<string, decimal> Price {get;set;}

In this case, price_ is used as a prefix to the actual Solr field name, e.g. with this
mapping,

Price["regular"]

maps to a Solr field named price_regular.

Another, even more flexible mapping:


[SolrField("*")]
public IDictionary<string, object> OtherFields {get;set;}

This acts as a catch-all container for any fields that are otherwise unmapped.
E.g.

OtherFields["price_i"]

maps to a Solr field named

price_i .

Fully loose mapping


An even more "dynamic" mapping can be achieved by using
a

Dictionary<string,object>

as document type. In this document type, the dictionary

key corresponds to the Solr field name and the value to the Solr field value.
Statically typing the fields is obviously lost in this case, though.
When adding documents as

Dictionary<string,object>

SolrNet will recognize field

value types as usual, e.g. you can use strings, int, collections, arrays, etc.
Example:
Startup.Init<Dictionary<string, object>>(serverUrl);
var solr =
ServiceLocator.Current.GetInstance<ISolrOperations<Dictionary<string,
object>>>();

solr.Add(new Dictionary<string, object> {


{"field1", 1},
{"field2", "something else"},
{"field3", new DateTime(2010, 5, 5, 12, 23, 34)},
{"field4", new[] {1,2,3}},
});

When fetching documents as

Dictionary<string,object>

SolrNet will automatically

map each field value to a .NET type, but it's up to you to downcast the field value
to a properly typed variable. Example:
ISolrOperations<Dictionary<string, object>> solr = ...
ICollection<Dictionary<string, object>> results = solr.Query(SolrQuery.All);
bool inStock = (bool) results[0]["inStock"];

Custom mapping
You can code your own mapping mechanism by implementing
the

IReadOnlyMappingManager

interface.

Overriding the default mapper


By default SolrNet maps Solr fields using attributes. However you might want to
use another mapper. Replacing the default mapper depends on how you set up the
library:

Built-in container
If you're using the default built-in container, you can replace it like this before
calling

Startup.Init() :

var mapper = new MappingManager();

/* Here come your mappings */


var container = new Container(Startup.Container);
container.RemoveAll<IReadOnlyMappingManager>();
container.Register<IReadOnlyMappingManager>(c => mapper);
ServiceLocator.SetLocatorProvider(() => container);
Startup.Init<Document>("http://localhost:8983/solr");

Windsor facility
If you're using the Windsor facility, you can override the mapper like this:
var mapper = new MappingManager();
/* Here come your mappings */
var solrFacility = new SolrNetFacility("http://localhost:8983/solr") {Mapper
= mapper};
var container = new WindsorContainer();
container.AddFacility("solr", solrFacility);

Ninject module
var mapper = new MappingManager();
/* Here come your mappings */
var c = new StandardKernel();
c.Load(new SolrNetModule("http://localhost:8983/solr") {Mapper = mapper});

3. Initialization
Once you have created your document class, you have to initialize the library in
order to operate against the Solr instance. This is usually done once at application
startup:

Startup.Init<Product>("http://localhost:8983/solr");

Then you ask the service locator for the SolrNet service instance which allows you
to issue any supported operation:
var solr = ServiceLocator.Current.GetInstance<ISolrOperations<Product>>();
solr.Delete(SolrQuery.All);
solr.Add(p);
solr.Commit();
var products = solr.Query(new SolrQueryByRange<decimal>("price", 10m,
100m));

Castle Windsor
Alternatively, if your app uses Castle Windsor you can set up SolrNet using the
included facility:
container.AddFacility("solr", new
SolrNetFacility("http://localhost:8983/solr"));

Or using Windsor's xml config:


<castle>
<facilities>
<facility id="solr"
type="Castle.Facilities.SolrNetIntegration.SolrNetFacility, SolrNet">
<solrURL>http://localhost:8983/solr</solrURL>
</facility>
</facilities>
</castle>

Ninject
If you're using Ninject, you can use the Ninject module:

kernel.Load(new SolrNetModule("http://localhost:8983/solr"));

StructureMap
If you are using StructureMap, you can use the StructureMap module
( StructureMap.SolrNetIntegration ):
ObjectFactory.Initialize(
x => x.AddRegistry(
new SolrNetRegistry("http://localhost:8893/solr")
)
);

Autofac
(SolrNet 0.4.0)
var builder = new ContainerBuilder();
builder.RegisterModule(new SolrNetModule("http://localhost:8983/solr"));
var container = builder.Build();

Unity
(SolrNet 0.4.0)
var solrServers = new SolrServers {
new SolrServerElement {
Id = "test",
Url = "htp://localhost:8893",
DocumentType = typeof (Entity).AssemblyQualifiedName,
}

};
container = new UnityContainer();
new SolrNetContainerConfiguration().ConfigureContainer(solrServers,
container);

Multi-core mapping
If you need to map multiple Solr cores/instances, see this page.

4. CRUD operations
Create/Update
Create and update are the same operation in Solr (and SolrNet). It's mapped to
the

Add()

method in the

ISolrOperations<T> interface.

There are two overloads of


other takes a
these

Add()

IEnumerable

Add() :

one takes a single document as parameter, the

of documents. Keep in mind that one call to any of

methods will end up in one HTTP request to Solr. So for example this:

ISolrOperations<MyDocument> solr = ...


for (var i = 0; i < 100; i++)
solr.Add(new MyDocument(i));

is not the same as this:


var documents = Enumerable.Range(0, 100).Select(i => new MyDocument(i));
solr.Add(documents);

If you

Add()

a document that already exists in the Solr index, it's replaced (using

the unique key as equality)


There's also

AddWithBoost()

(also with single-document and

IEnumerable

overloads)

that you can use to apply an index-time boosting to the added documents.
Add()

supports the

commitWithin

more information about them.

Retrieve

and

overwrite

parameters. See the Solr wiki for

See Querying

Delete
The ISolrOperations interface has several

Delete()

Delete(T doc) :

Delete(string id) :

Delete(IEnumerable<T> docs) :

Delete(IEnumerable<string> ids) :

Delete(ISolrQuery q) :

Delete(IEnumerable<string> ids, ISolrQuery q) :

overloads:

deletes a single document using its unique key to identify it.


deletes a single document with a unique key
deletes a batch of documents in a single shot.
deletes a batch of documents in a single shot.

deletes all documents that match a query.


deletes a batch of documents and all

documents that match a query in a single shot.

Commit and Optimize


After issuing any number of

Add()

or

Delete()

operations, be sure to call

Commit() :

solr.Add(myDocument);
solr.Add(anotherDocument);
solr.Delete(oldDocument);
solr.Commit();

... this tells Solr to finalise the changes you have made and to start rebuilding
indexes and related data.
Obviously this has a performance penalty on the Solr instance as all query caches
are cleared and repopulated due to the new index.
Alternatively, you can let Solr manage commits, enabling the autoCommit
options in the Solr configuration.
The

Optimize()

method:

solr.Optimize();

... issues a command to tell Solr to begin optimizing its indexes. Again this is an
expensive operation in terms of the Solr instance and shouldn't be called too
frequently.
Additional information on committing and optimization considerations can be
found here

Rollback
Rollback()

rollbacks all add/deletes made to the index since the last commit. Note

that this is nothing like the rollback of relational databases. See the Solr wiki for
more information.

5. Querying
Simple query
This is the simplest 'query object' in SolrNet. Whatever you give it is passed
straight to Solr's q parameter
ISolrOperations<Product> solr = ...
var products1 = solr.Query(new SolrQuery("lucene")); // search for "lucene" in the
default field
var products2 = solr.Query(new SolrQuery("name:solr")); // search for "solr" in the
"name" field

Query by field
This allows you to define field name and value separately:
ISolrOperations<Product> solr = ...
var products = solr.Query(new SolrQueryByField("name", "solr")); // search for "solr"
in the "name" field

It also has the benefit that it handles special character escaping for you.
(SolrNet 0.4.0) You can disable character escaping by setting

Quoted = false :

var q = new SolrQueryByField("name", "John*") { Quoted = false };

Query by range
Creates a range query:
ISolrOperations<Product> solr = ...
var products = solr.Query(new SolrQueryByRange<decimal>("price", 100m, 250.50m)); //
search for price between 100 and 250.50

Query by list of values


var q = new SolrQueryInList("name", "solr", "samsung", "maxtor");

is the same as

name:solr OR name:samsung OR name:maxtor

"Any value" query


It's often convenient to see what documents have a field defined or not:
var q = new SolrHasValueQuery("name");

is equivalent to the Solr query

name:[* TO *]

Query by distance
(SolrNet 0.4.0)
Creates a

geofilt

or

bbox

filter on a LatLonType field.

Examples:
// default accuracy is CalculationAccuracy.Radius (higher accuracy)
var q = new SolrQueryByDistance("store", pointLatitude = 45.15, pointLongitude = 93.85, distance = 5);
var q = new SolrQueryByDistance("store", pointLatitude = 45.15, pointLongitude = 93.85, distance = 5, accuracy = CalculationAccuracy.BoundingBox);

See the Solr wiki for more information.

Query operators
You can use the

&&

and

||

operators to connect queries, with the expected results:

var q = new SolrQuery("solr") && new SolrQuery("name:desc");

generates the query

solr AND name:desc

The plus (+) operator is also overloaded. It concatenates the queries and leaves
the actual operator to the default as specified in Solr's configuration.
var q = new SolrQuery("solr") + new SolrQuery("name:desc");

creates the query

solr name:desc

To negate a query, you can call

Not()

on it or just use the

operator:

var q = !new SolrQuery("solr");

creates the query

-solr

Finally, the minus (-) operator:


var q = new SolrQuery("solr") - new SolrQuery("name:desc"); // solr - name:desc

which is equivalent to (and more intuitive than):


var q = new SolrQuery("solr") + !new SolrQuery("name:desc"); // solr - name:desc

Alternatively, if you have a list of queries you want to aggregate you can
use

SolrMultipleCriteriaQuery .

For example:

new SolrMultipleCriteriaQuery(new[] {new SolrQuery("1"), new SolrQuery("2")})

is the same as:


new SolrQuery("1") + new SolrQuery("2")

You can also define what operators to use to join these queries, e.g:
new SolrMultipleCriteriaQuery(new[] {new SolrQuery("1"), new SolrQuery("2")}, "AND")

Boosting
You can boost particular queries by calling

Boost() ,

for example:

var q = new SolrQuery("name:desc").Boost(2); // (name:desc)^2

See the Lucene docs for more information about boosting.

DSL
See the fluent API documentation for an alternative way of expressing queries.

Filter queries
Filter queries can be used to specify a query that can be used to restrict the super
set of documents that can be returned, without influencing score.
ISolrOperations<Product> solr = ...
var products = solr.Query(SolrQuery.All, new QueryOptions {
FilterQueries = new ISolrQuery[] {
new SolrQueryByField("manu", "apache"),
new SolrQueryByRange<decimal>("price", 100m, 200m),
}
});

More information in the Solr wiki.

Fields
By default Solr returns all stored fields. You can retrieve only selected fields
instead:
ISolrOperations<Product> solr = ...
var products = solr.Query(SolrQuery.All, new QueryOptions {
Fields = new[] {"id", "manu"}
});

Sorting

By default Solr returns search results ordered by "score desc". You can sort the
results by any field(s):
ISolrOperations<Product> solr = ...
var products = solr.Query(SolrQuery.All, new QueryOptions {
OrderBy = new[] {new SortOrder("manu", Order.DESC), SortOrder.Parse("id
asc")}
});

You can random sort using

RandomSortOrder :

solr.Query(SolrQuery.All, new QueryOptions {


OrderBy = new[] {new RandomSortOrder("randomF")},
});

where

randomF

is a random sort field.

RandomSortOrder

has various constructors to

generate a random seed (as in the example above) or use a predefined seed.

Pagination
In Solr you can't retrieve all your documents in single query. However, by default
SolrNet will try to retrieve a large amount of documents, trying to mimic the
behavior of a RDBMS without a TOP clause. It's not recommended to rely on
this behavior. Instead, always define pagination parameters, for example:
ISolrOperations<Product> solr = ...
solr.Query("somequery", new QueryOptions{
Start = 10,
Rows = 25
});

This will fetch at most 25 documents, starting from the 10th document in the total
result set.

Additional parameters
Solr has lots of features that aren't directly mapped in SolrNet, but you can enable
and use most of them with the

ExtraParams dictionary.

Parameters defined

in

ExtraParams

are directly passed to the Solr querystring. For example you

can restrict the maximum time allowed for a query:


ISolrOperations<Product> solr = ...
var products = solr.Query(SolrQuery.All, new QueryOptions {
ExtraParams = new Dictionary<string, string> {
{"timeAllowed", "100"}
}
});

Or enable DisMax instead of the standard request handler:


ISolrOperations<Product> solr = ...
var products = solr.Query(SolrQuery.All, new QueryOptions {
ExtraParams = new Dictionary<string, string> {
{"qt", "dismax"}
}
});

LocalParams
LocalParams provide a way to add certain metadata to a piece of query. It's used
among other things to change the default operator type on the fly for a particular
query.
In SolrNet, LocalParams are represented by the
basically a

Dictionary<string, string> .

LocalParams

class, which is

LocalParams are attached to a query using

the "+" operator. Here's an example:


solr.Query(new LocalParams {{"type", "dismax"},{"qf", "myfield"}} + new
SolrQuery("solr rocks"));

This will generate:

q={!type=dismax qf=myfield}solr rocks

More information about LocalParams in the Solr wiki.

6. Faceting
SolrNet supports faceted searching.

There are basically three kinds of facet queries:


1. querying by field
2. date facets
3. arbitrary facet queries
Facet queries are issued through the
the

QueryOptions

FacetQueries

property of

QueryOptions .

Then

instance is passed to the server instance.

Querying by field
Querying by field is handled by the
through the

FacetFields

SolrFacetFieldQuery

class. Results are available

property.

Example: print all categories sorted by popularity.


ISolrOperations<Document> solr = ...
var r = solr.Query(SolrQuery.All, new QueryOptions {
Rows = 0,
Facet = new FacetParameters {
Queries = new[] {new SolrFacetFieldQuery("category")}
}
});
foreach (var facet in r.FacetFields["category"]) {
Console.WriteLine("{0}: {1}", facet.Key, facet.Value);
}

Date facets
Date facet queries create facets from date ranges. Sample code:
ISolrOperations<Product> solr = ...

var results = solr.Query(SolrQuery.All, new QueryOptions {


Facet = new FacetParameters {
Queries = new[] {
new SolrFacetDateQuery("timestamp", new DateTime(2001, 1,
1).AddDays(-1) /* range start */, new DateTime(2001, 1, 1).AddMonths(1) /*
range end */, "+1DAY" /* gap */) {
HardEnd = true,
Other = new[] {FacetDateOther.After, FacetDateOther.Before}
},
}
}
});
DateFacetingResult dateFacetResult = results.FacetDates["timestamp"];
foreach (KeyValuePair<DateTime, int> dr in dateFacetResult.DateResults) {
Console.WriteLine(dr.Key);
Console.WriteLine(dr.Value);
}

Arbitrary facet queries


Arbitrary facet queries are handled by the
available through the

FacetQueries

SolrFacetQuery

class. Results are

property.

Example: segregate items by price (less than $500 - more than $500)
ISolrOperations<Document> solr = ...
var lessThan500 = new SolrQueryByRange<decimal>("price", 0m, 500m);
var moreThan500 = new SolrQueryByRange<string>("price", "500", "*");

var r = solr.Query(SolrQuery.All, new QueryOptions {


Rows = 0,
Facet = new FacetParameters {
Queries = new[] {new SolrFacetQuery(lessThan500), new
SolrFacetQuery(moreThan500)}
}
});
foreach (var facet in r.FacetQueries) {
Console.WriteLine("{0}: {1}", facet.Key, facet.Value);
}

Pivot faceting
http://wiki.apache.org/solr/HierarchicalFaceting#Pivot_Facets
http://wiki.apache.org/solr/SimpleFacetParameters#Pivot_.28ie_Decision_Tree.29
_Faceting
(to be documented)

7. Highlighting
This feature will "highlight" (typically with markup) the terms that matched the
query, including a snippet of the text around the matched term.
Sample code:
var results = solr.Query(new SolrQueryByField("features", "noise"), new
QueryOptions {
Highlight = new HighlightingParameters {
Fields = new[] {"features"},
}

});
foreach (var h in results.Highlights[results[0].Id]) {
Console.WriteLine("{0}: {1}", h.Key, string.Join(", ",
h.Value.ToArray()));
}

would print for example:


features: <em>Noise</em>Guard, SilentSeek technology, Fluid Dynamic Bearing (FDB)
motor

Solr reference documentation: http://wiki.apache.org/solr/HighlightingParameters

8. More-like-this
This feature returns a list of similar documents for each document returned in the
results of the original query.
Parameters are defined through the

MoreLikeThis

property of the

QueryOptions .

Example: searching for "apache", for each document in the result search for similar
documents in the "cat" (category) and "manu" (manufacturer) fields:
ISolrBasicOperations<Product> solr = ...
var results = solr.Query(new SolrQuery("apache"), new QueryOptions {
MoreLikeThis = new MoreLikeThisParameters(new[] {"cat", "manu"}) {
MinDocFreq = 1, // minimum document frequency
MinTermFreq = 1, // minimum term frequency
},
});
foreach (var r in results.SimilarResults) {

Console.WriteLine("Similar documents to {0}", r.Key.Id);


foreach (var similar in r.Value)
Console.WriteLine(similar.Id);
Console.WriteLine();
}

All parameters defined in the Solr docs are supported.

9. Spell checking
You'll need to install the

SpellCheckComponent

in the standard request handler in

order to use this.


Next, a spellcheck dictionary must be provided. Normally a default dictionary is
created by invoking

BuildSpellCheckDictionary()

at commit/optimize time (you can

also configure Solr to automatically rebuild spellchecking indices):


ISolrOperations<Product> solr = ...
solr.BuildSpellCheckDictionary();

Now you can start issuing spellchecking queries by defining


the

SpellCheck

parameter in the

QueryOptions :

ISolrOperations<Product> solr = ...


var results = solr.Query("ipo appl", new QueryOptions {
SpellCheck = new SpellCheckingParameters {Collate = true}
});

Then you get the suggestions from

results.SpellChecking ,

foreach (var sc in results.SpellChecking) {


Console.WriteLine("Query: {0}", sc.Query);

i.e.:

foreach (var s in sc.Suggestions) {


Console.WriteLine("Suggestion: {0}", s);
}
}

which would print something like:


Query: ipo
Suggestion: ipod
Query: appl
Suggestion: apple

All of the SpellCheckComponent parameters are supported, except for


the

extendedResults

10.

option.

Stats

Property

Description

Min

The minimum value

Max

The maximum value

Sum

Sum of all values

Count

How many (non-null) values

Missing

How many null values

Property

Description

SumOfSquares

Sum of all values squared (useful for stddev)

Mean

The average (v1+v2...+vN)/N

Standard Deviation -- measuring how widely spread the

StdDev

values in a data set are.

Sample usage:
ISolrOperations<Product> solr = ...
var results = solr.Query(SolrQuery.All, new QueryOptions {
Rows = 0,
Stats = new StatsParameters {
Facets = new[] { "inStock" },
FieldsWithFacets = new Dictionary<string, ICollection<string>> {
{"popularity", new List<string> {"price"}}
}
}
});

foreach (var kv in results.Stats) {


Console.WriteLine("Field {0}: ", kv.Key);
var s = kv.Value;

Console.WriteLine("Min: {0}", s.Min);


Console.WriteLine("Max: {0}", s.Max);
Console.WriteLine("Sum of squares: {0}", s.SumOfSquares);
foreach (var f in s.FacetResults) {
Console.WriteLine("Facet: {0}", f.Key);
foreach (var fv in f.Value) {
Console.WriteLine("Facet value: {0}", fv.Key);
Console.WriteLine("Min: {0}", fv.Value.Min);
Console.WriteLine("Max: {0}", fv.Value.Max);
Console.WriteLine("Sum of squares: {0}", fv.Value.SumOfSquares);
}
}
}

For more details see the Solr wiki.

11.

Field collapsing / grouping

This feature can be used to collapse - or group - documents by the unique values
of a specified field. Included in the results are the number of records by document
key and by field value.

For Solr 1.4 / 3.1


This feature is not included with the stock Solr 1.4 or 3.1. You need to apply
a patch and recompile Solr.
Sample code:
ISolrOperations<Product> solr = ...

var results = solr.Query(new SolrQueryByField("features", "noise"), new


QueryOptions {
Collapse = new CollapseParameters {
Field = "manu"
}
});
foreach (KeyValuePair<string, int> collapsedDocument in
results.Collapsing.DocResults)
Console.WriteLine("Collapse count for document '{0}': {1}",
collapsedDocument.Key, collapsedDocument.Value);

For more details see:

http://wiki.apache.org/solr/FieldCollapsing

https://issues.apache.org/jira/browse/SOLR-236

http://blog.jteam.nl/2009/10/20/result-grouping-field-collapsing-with-solr/

For Solr 3.3+


Use

Grouping

instead of

Collapse

in

QueryOptions

with SolrNet 0.4.0 alpha 1 or

later. Sample code

12.

Core admin

SolrNet offers some facilities to execute Solr core admin commands. See the Solr
wiki for detailed explanations of these commands.
First, build an instance of

ISolrCoreAdmin :

const string solrUrl = "http://localhost"8983/solr";


var headerParser = ServiceLocator.Current.GetInstance<ISolrHeaderResponseParser>();
var statusParser = ServiceLocator.Current.GetInstance<ISolrStatusResponseParser>();
ISolrCoreAdmin solrCoreAdmin = new SolrCoreAdmin(new SolrConnection(solrUrl),
headerParser, statusParser);
ISolrCoreAdmin

can execute the following core admin commands:

Status
/// Get the status of all registered cores:
IList<CoreResult> coreStatus = solrCoreAdmin.Status();
/// Get the status of a single core:
var coreStatus = solrCoreAdmin.Status("core1");

Create
solrCoreAdmin.Create(coreName: "items", instanceDir: "items");

Reload
solrCoreAdmin.Reload("core1");

Rename
solrCoreAdmin.Rename("core1", "newCoreName");

Swap
solrCoreAdmin.Swap("core0", "core1");

Unload
solrCoreAdmin.Swap("core0", UnloadCommand.Delete.Data);

Merge
solrCoreAdmin.Merge("destinationCore", new MergeCommand.SrcCore("sourceCore0"), new
MergeCommand.SrcCore("sourceCore1"));

Alias
solrCoreAdmin.Alias("existingCore", "alias");

13.

Fluent API

Query-building
Some examples:
Query.Simple("name:solr"); // name:solr
Query.Field("name").Is("solr"); // name:solr
Query.Field("price").From(10).To(20); // price:[10 TO 20]
Query.Field("price").In(10, 20, 30); // price:10 OR price:20 OR price:30
Query.Field("name").HasAnyValue(); // name:[* TO *]

These can be used anywhere where a

ISolrQuery

is accepted, e.g.:

ISolrOperations<Product> solr = ...


solr.Query(Query.Field("name").HasAnyValue());

They can also be mixed with boolean operators:


ISolrOperations<Product> solr = ...
solr.Query(Query.Field("name").HasAnyValue() && Query.Field("price").Is(0));

Querying
This API is deprecated. Please don't use it. If you're using it, be aware that it will be
removed in a future release of SolrNet.
Example:
[SetUp]
public void setup() {
Solr.Connection = new SolrConnection("http://localhost:8983/solr");
}

[Test]
public void QueryById() {
ISolrQueryResults<TestDocument> r =
Solr.Query<TestDocument>().By("id").Is("123456").Run();
}

[Test]
public void QueryByRange() {
ISolrQueryResults<TestDocument> r =
Solr.Query<TestDocument>().By("id").Between(123).And(456).OrderBy("id",
Order.ASC).Run();
}

[Test]
public void DeleteByQuery() {
Solr.Delete.ByQuery<TestDocument>("id:123456");

Run()

is the explicit kicker method. There are some more examples in the tests.

14.

Overriding the default mapper

By default SolrNet maps Solr fields using attributes. However you might want to
use another mapper. Replacing the default mapper depends on how you set up the
library:

Built-in container
If you're using the default built-in container, you can replace it like this before
calling

Startup.Init() :

var mapper = new MappingManager();


/* Here come your mappings */
var container = new Container(Startup.Container);
container.RemoveAll<IReadOnlyMappingManager>();
container.Register<IReadOnlyMappingManager>(c => mapper);
ServiceLocator.SetLocatorProvider(() => container);
Startup.Init<Document>("http://localhost:8983/solr");

Windsor facility
If you're using the Windsor facility, you can override the mapper like this:
var mapper = new MappingManager();
/* Here come your mappings */
var solrFacility = new SolrNetFacility("http://localhost:8983/solr") {Mapper
= mapper};
var container = new WindsorContainer();

container.AddFacility("solr", solrFacility);

Ninject module
var mapper = new MappingManager();
/* Here come your mappings */
var c = new StandardKernel();
c.Load(new SolrNetModule("http://localhost:8983/solr") {Mapper = mapper});

15.

NHibernate-SolrNet integration

A NHibernate-SolrNet module is included in SolrNet, with these features:

Automatic database -> Solr synchronization

Issue Solr queries from NHibernate's ISession


This is intended to be used on entities that are similarly mapped on both
NHibernate and SolrNet.
This integration is deprecated. It is not generic enough to be truly reusable for most
non-trivial cases. It will be removed in a future release of SolrNet.

Setup
Configure SolrNet and NHibernate as usual. Then apply SolrNet's integration to
NHibernate's configuration like this:
NHibernate.Cfg.Configuration cfg = SetupNHibernate();
var cfgHelper = new NHibernate.SolrNet.CfgHelper();
cfgHelper.Configure(cfg, true); // true -> autocommit Solr after every
operation (not really recommended)

If you're not using the default built-in container, you have to tell
e.g.:

CfgHelper

to use it,

IWindsorContainer container = new WindsorContainer();


...
var cfgHelper = new NHibernate.SolrNet.CfgHelper(container);
...

Usage
Whenever a NHibernate entity that is also mapped in SolrNet is created, updated,
or deleted, it will be automatically updated on Solr. NHibernate transactions are
honored: entities are updated on Solr only when the NHibernate transaction is
committed.
This synchronization goes only in the direction NHibernate -> SolrNet, not the
other way around, i.e. operations issued directly through ISolrOperations will not
be reflected on NHibernate.
In order to issue Solr queries through NHibernate, the ISession needs to be
wrapped. Sample:
ISessionFactory sessionFactory = ...
using (var session = cfgHelper.OpenSession(sessionFactory)) {
ICollection<Entity> entities = session.CreateSolrQuery("this is a fulltext query").SetMaxResults(10).List<Entity>();
}

16.

Multi-core / multi-instance

This page describes how to configure SolrNet to access (read/write) multiple Solr
cores or instances. It assumes you know what Solr cores are, how to configure and
use them outside of SolrNet. This page doesn't
cover CoreAdminHandler commands.

How to configure SolrNet for multicore depends on how it's integrated to your app,
and if your cores map to different types or the same type.

With built-in container


The built-in container ( Startup ) is currently limited to access multiple
cores/instances with different mapped types. It's quite simple to configure:
assuming you have a core that maps to class Product and another core mapping
to class

Person :

Startup.Init<Product>("http://localhost:8983/solr/product");
Startup.Init<Person>("http://localhost:8983/solr/person");

ISolrOperations<Product> solrProduct =
ServiceLocator.Current.GetInstance<ISolrOperations<Product>>();
solrProduct.Add(new Product { Name = "Kodak EasyShare" });
solrProduct.Commit();

ISolrOperations<Person> solrPerson =
ServiceLocator.Current.GetInstance<ISolrOperations<Person>>();
solrPerson.Add(new Person { FirstName = "Joe", LastName = "Example" });
solrPerson.Commit();

With Windsor facility


Code config:
var solrFacility = new
SolrNetFacility("http://localhost:8983/solr/defaultCore");
solrFacility.AddCore("core0-id", typeof(Product),
"http://localhost:8983/solr/product");

solrFacility.AddCore("core1-id", typeof(Product),
"http://localhost:8983/solr/product2");
solrFacility.AddCore(typeof(Person), "http://localhost:8983/solr/person");
// no need to set an explicit ID since it's the only core for Person
container.AddFacility("solr", solrFacility);

ISolrOperations<Person> solrPerson =
container.Resolve<ISolrOperations<Person>>();
ISolrOperations<Product> solrProduct1 =
container.Resolve<ISolrOperations<Product>>("core0-id"); // use proper
Windsor service overrides instead of resolving like this
ISolrOperations<Product> solrProduct2 =
container.Resolve<ISolrOperations<Product>>("core1-id");

Equivalent XML config:


<facilities>
<facility id='solr'>
<solrURL>http://localhost:8983/solr/defaultCore</solrURL>
<cores>
<core id='core0-id'>
<documentType>Namespace.To.Product, MyAssembly</documentType>
<url>http://localhost:8983/solr/product</url>
</core>
<core id='core1-id'>
<documentType>Namespace.To.Product, MyAssembly</documentType>
<url>http://localhost:8983/solr/product2</url>
</core>
<core>
<documentType>Namespace.To.Person, MyAssembly</documentType>
<url>http://localhost:8983/solr/person</url>
</core>
</cores>
</facility>
</facilities>

With StructureMap registry

var cores = new SolrServers {


new SolrServerElement {
Id = "core0-id",
DocumentType = typeof(Product).AssemblyQualifiedName,
Url = "http://localhost:8983/solr/product",
},
new SolrServerElement {
Id = "core1-id",
DocumentType = typeof(Person).AssemblyQualifiedName,
Url = "http://localhost:8983/solr/person",
},
};
ObjectFactory.Initialize(c => c.IncludeRegistry(new
SolrNetRegistry(cores)));
var solr1 = ObjectFactory.GetInstance<ISolrOperations<Product>>();
var solr2 = ObjectFactory.GetInstance<ISolrOperations<Person>>();

Equivalent XML config (in App.config):


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="solr"
type="StructureMap.SolrNetIntegration.Config.SolrConfigurationSection,
StructureMap.SolrNetIntegration" />
</configSections>
<solr>

<server id="core0-id"
url="http://localhost:8080/solr/product"
documentType="Namespace.To.Product,
MyAssembly" />
<server id="core1-id"
url="http://localhost:8080/solr/person"
documentType="Namespace.To.Person,
MyAssembly" />
</solr>
</configuration>

17.

Schema/mapping validation

SolrNet aims to be flexible to map a Solr schema in different ways depending on


the project needs. A single schema may be mapped in several different ways even
within a single project. However, there are invalid mappings that will end up in
runtime errors, and it may not be evident from these errors that the problem lies in
the mapping.
SolrNet offers a facility to aid with the detection of mapping mismatches. Example:
ISolrOperations<Document> solr = ...
IList<ValidationResult> mismatches =
solr.EnumerateValidationResults().ToList();
var errors = mismatches.OfType<ValidationError>();
var warnings = mismatches.OfType<ValidationWarning>();
foreach (var error in errors)
Console.WriteLine("Mapping error: " + error.Message);
foreach (var warning in warnings)
Console.WriteLine("Mapping warning: " + warning.Message);

if (errors.Any())
throw new Exception("Mapping errors, aborting");

18.

Sample application

A sample web application is provided to demo some of the library's features. This
sample application is not meant to be an absolute reference of SolrNet integration
with your application. Real world applications are likely to need much more
complex interactions with SolrNet.
Screenshot:

Requirements:

.NET 3.5 SP1

ASP.NET MVC 2.0

Java 1.5 or better

After downloading and extracting the sample app, run

runsample.bat

to fire up the

included Solr and Cassini.

19.
Frequently asked questions about
SolrNet
Is SolrNet a .NET port of Solr?
No, SolrNet is a HTTP client so you can talk to Solr from your .NET application. If
you want to run Solr on .NET, take a look at thisblog post.

What version of Solr do I need? Does SolrNet work with the latest
release of Solr?
It's very uncommon for Solr to make breaking changes in core functionality
between releases, so SolrNet should work with any fairly recent version of Solr
(3.1+). Note that it's up to you to use features that are supported by your Solr
version. For example, you will get an error if you try to use grouping with Solr 3.1

I'm getting a Bad Request error when calling

Commit()

You're probably using an outdated version. Upgrade to a recent build

I'm getting a 404 (not found) response from Solr when doing any
operation with SolrNet
You're probably missing the core name in the URL you're passing to Startup.Init

I'm getting an error "'SolrConnection' already registered in


container"
Make sure you call

Startup.Init

only once per document type in your application.

I created my SolrNet document type, but when I add an instance


to Solr I get an 'unknown field' error.
You need to edit the schema.xml in Solr to reflect the fields mapped in your .NET
document type. Schema reference:

http://wiki.apache.org/solr/SchemaXml

https://cwiki.apache.org/confluence/display/solr/Overview+of+Documents,+Fields,+
and+Schema+Design
You can also use the schema/mapping validation for guidance.

How do I get the

score

pseudo-field?

Solr does not return the score by default, so you have to fetch it explicitly. In
SolrNet this translates to

Fields = new[] {"*","score"}

in your

QueryOptions

instance.

Also, you have to map it to a property in your document type. For example:
[SolrField("score")]
double? Score { get; set; }

I'm getting a "URI too long" error


You're hitting the GET request limit of the Solr web container (i.e. Jetty or Tomcat).
You can either:

edit this limit in the web container configuration, or

make SolrNet use POST instead of GET. You can do this by installing the
SolrNet.Impl.PostSolrConnection decorator (here's an example)

You might also like