0

Sitecore search & Solr – ordering by score with your own POCO

Recently I had a requirement to search for content (e.g events) within the given area of a map. One of the great features in Solr is it’s array of functions that can be run against documents. Specifically there are a set of functions to do with spatial search. Out of the box Sitecore’s search API doesn’t support running custom Solr functions. Unfortunately it’s quite difficult to extend the search API, you’re required to replace and override several classes. Thankfully there’s a Solr Spatial Search Support module, created by Ehab ElGindy, which works like a charm :). You can read more about the module on Ehab’s blog.

With the back story in place I did come across an issue, and this is no sleight on the module, which all revolves around the score field. If you’re running a standard query, using the Sitecore search API,  Solr will score the documents for you and then they’ll be ordered by score field. When using the new WithinRadius method, provided by the module, it replaces the value of the score field by the distance from the center point. By default the results won’t be ordered by score which is why the module also provides the OrderByNearest method. You can see on GitHub that all the method is doing is using the OrderBy method and specifying the score field via the indexers. The problem for me was that all the POCO’s I’m using don’t inherit from the SearchResultItem class provided by Sitecore. I thought the simple solution would be to add a Score property to the POCO along with specifying it in the configuration. This is how you tell Sitecore not to change the index field name you specify. If you don’t specify the configuration part it will add on _t (score_t) as it will think it’s a dynamic index field in Solr.

public interface IMyPocoInterface
{
	[IndexField("score")]
	string Score { get; }
}

Configuration

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<contentSearch>
			<indexConfigurations>
				<defaultSolrIndexConfiguration>
					<fieldMap>
						<fieldNames>
							<field fieldName="score" returnType="string" />
						</fieldNames>
					</fieldMap>
				</defaultSolrIndexConfiguration>
			</indexConfigurations>
		</contentSearch>
	</sitecore>
</configuration>

Unfortunately, to my surprise, this didn’t work and when I used OrderBy method on my queryable. The query being created for Solr had the _t appended to the score index field name in the sort parameter.

queryable.OrderBy(i => i.Score);

Given I needed to be able to sort by score I tried a couple ways to solve the issue :). Both of attempts revolved around replicating what the original OrderByDistance method does, which is using the indexers. None of the following worked :(, both of the attempts resulted in the _t being appended to the score index field name.

1) Implement an indexer on the POCO.

public interface IMyPocoInterface
{
	string this[string indexer] { get; }
}

public class MyPoco : IMyPocoInterface
{
	public string this[string indexer] { get { return string.Empty(); } }
}

2) Implement the IObjectIndexers interface which the SearchResultItem class also implements. This is the interface which allows you to use the indexer in your query.

public interface IMyPocoInterface, IObjectIndexers
{	
}

public class MyPoco : IMyPocoInterface
{
	public object this[ObjectIndexerKey key] { get { return string.Empty(); } set { return; } }
	public string this[string key] { get { return string.Empty(); } set { return; } }
}

I got in contact with Stephen Pope and Elhab to try and figure out what was going on. Apparently Sitecore isn’t respecting the configuration when you use your index field for sorting. In the standard query (e.g. .Where(i => i.MyField) and MyField is specified as myfield in the configuration) you would get your correctly mapped index field name. On top of this it would appear that using the indexer to specify the index field works differently when you’re using it via a class that inherits the SearchResultItem class. Hence the reason why my two efforts above didn’t work but the extension method in the module does.

One thing that came out of the conversation with Stephen is that when Sitecore starts it reads the Solr schema file. From there it loads in any fields specified and won’t change those (e.g. add _t) if they’re used in a query. More importantly it also won’t change them if used when sorting.

Finally we get to a solution… Add the score field (which isn’t really a real field) to the Solr schema.

<field name="score" type="tdouble" indexed="true" stored="false" />

Personally I thought Solr would blow up when I did this 🙂 and it feels really really wrong to have done it! Yet to my surprise it didn’t, I’ve seen no side effects and I can now sort by score. Sometimes the solution which looks and appears so wrong is also so right. If it works, it’s right!

Jason Bert