SolrNet Documentation
SolrNet Documentation
SolrNet Documentation
[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();
}
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
[SolrField("manu_exact")]
public string Manufacturer { 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.
}
AttributesMappingManager
class.
.. 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
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; }
}
Manual mapping
This allows you to programmatically define the field for each property:
public class Product {
type="integer"
indexed="true"
[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"]
This acts as a catch-all container for any fields that are otherwise unmapped.
E.g.
OtherFields["price_i"]
price_i .
Dictionary<string,object>
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>
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>>>();
Dictionary<string,object>
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.
Built-in container
If you're using the default built-in container, you can replace it like this before
calling
Startup.Init() :
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"));
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.
Add()
IEnumerable
Add() :
methods will end up in one HTTP request to Solr. So for example this:
If you
Add()
a document that already exists in the Solr index, it's replaced (using
AddWithBoost()
IEnumerable
overloads)
that you can use to apply an index-time boosting to the added documents.
Add()
supports the
commitWithin
Retrieve
and
overwrite
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) :
overloads:
Add()
or
Delete()
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 :
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
is the same as
name:[* TO *]
Query by distance
(SolrNet 0.4.0)
Creates a
geofilt
or
bbox
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);
Query operators
You can use the
&&
and
||
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");
solr name:desc
Not()
operator:
-solr
Alternatively, if you have a list of queries you want to aggregate you can
use
SolrMultipleCriteriaQuery .
For example:
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:
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),
}
});
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")}
});
RandomSortOrder :
where
randomF
RandomSortOrder
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
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
6. Faceting
SolrNet supports faceted searching.
QueryOptions
FacetQueries
property of
QueryOptions .
Then
Querying by field
Querying by field is handled by the
through the
FacetFields
SolrFacetFieldQuery
property.
Date facets
Date facet queries create facets from date ranges. Sample code:
ISolrOperations<Product> solr = ...
FacetQueries
SolrFacetQuery
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", "*");
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()));
}
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) {
9. Spell checking
You'll need to install the
SpellCheckComponent
BuildSpellCheckDictionary()
SpellCheck
parameter in the
QueryOptions :
results.SpellChecking ,
i.e.:
extendedResults
10.
option.
Stats
Property
Description
Min
Max
Sum
Count
Missing
Property
Description
SumOfSquares
Mean
StdDev
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"}}
}
}
});
11.
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.
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/
Grouping
instead of
Collapse
in
QueryOptions
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 :
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 *]
ISolrQuery
is accepted, e.g.:
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.
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() :
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
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,
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.
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();
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");
<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
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:
runsample.bat
to fire up the
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
Commit()
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
Startup.Init
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.
score
pseudo-field?
Solr does not return the score by default, so you have to fetch it explicitly. In
SolrNet this translates to
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; }
make SolrNet use POST instead of GET. You can do this by installing the
SolrNet.Impl.PostSolrConnection decorator (here's an example)