Navigation and service panel


Content

This text is fallbacked from the German Version. If you need use Google Translate


Content Search mit Lucene und Sitecore 7

By Martin Haas on 25. September 2013, No comments

Nachdem in diesem Blog bereits die Indexierung in Sitecore 7 vorgestellt worden ist, befasst sich dieser Beitrag mit der Suche an sich.

Für die Suchseite wird am besten ein ControllerRendering verwendet, in welchem die Suchresultate generiert und als Model einer View übergeben werden.

Search-Context

Durch den Search-Context weiss Sitecore in welchem Index gesucht werden muss. Um den Search-Context zu generieren, muss lediglich die Id des Index bekannt sein.

var index = ContentSearchManager.GetIndex("website-index");

using (var context = index.CreateSearchContext())
{
    // Hier kommt die Suche
}

Index durchsuchen

Mit der Methode GetQueryable des Search-Contexts kann die Suche abgesetzt werden. Um die Suche einzugrenzen, können spezielle LINQ-Querys benutzt werden.

context.GetQueryable<SearchResult>(new CultureExecutionContext(Context.Language.CultureInfo))
    .Where(
        result => result.Content == query)
    .GetResults();

Hier gibt es einiges zu erklären:

SearchResult

Das SearchResult ist das Model eines Treffers. Wenn das Feld im Index-Document gespeichert wird (Feld in der Konfigratuion auf storageType="YES" setzen), werden die Properties des Models automatisch auf die Felder gemappt. Sollten Leerschläge oder andere für Properties ungültige Zeichen in den Feldern verwendet werden, kann das Mapping mittels Annotation bewerkstelligt werden

[IndexField("End Date")]
public DateTime EndDate { get; set; }

Der CultureExecutionContext

Der CultureExecutionContext ist dazu da, um die Suche zu lokalisieren. Dies bedeutet, dass zum Beispiel gewisse Stopwords der entsprechenden Sprache berücksichtigt werden. Dies funktioniert nur, wenn in der Konfiguration des Indexes der entsprechende Analyzer auch auf den Namen der CultureInfo gemappt ist.

result.Content

Das Index-Feld Content ist im eigentlichen Lucene-Index nicht vorhanden. Es ist vielmehr ein Zusammenschluss aller Felder, ausser den Computed Fields.

GetResults()

GetResults liefert ein Objekt des Typs SearchResults zurück, welche die Treffer (Hits) und die Anzahl Resultate (TotalSearchResults) enthält. Wichtig zu wissen ist, dass dieses Objekt ausserhalb des Such-Contexts nicht mehr existiert, also muss man die gefundenen Dokumente zwischenspeichern (sehr stark vereinfachtes Code-Beispiel):

SearchResultViewModel model = new SearchResultViewModel();
List<SearchResult> resultList = new List<SearchResult>();
int totalResults = 0;

using (var context = index.CreateSearchContext())
{
    var results = SearchUtil.GetResults(context, query);

    var hits = results.Hits.ToList();
    if (hits.Any())
	{
		totalResults = results.TotalSearchResults;

		foreach (var hit in hits)
        {
            resultList.Add(hit.Document);
        }
    }
}

model.SearchResults = resultList;
model.TotalResults = totalResults;
return this.View(model);

Erweiterte Querys

Die LINQ-Implementation der ContentSearch-Querys beruht vor allem auf String-Vergleichen. Auch ist es nicht möglich, und-/oder-Verknüpfungen wie in normalen LINQ-Querys zu benutzen. Dafür stellt Sitecore dem Entwickler aber den PredicateBuilder zur Verfügung, welcher solche Verknüpfungen bewerkstelligen kann.

Beim Erstellen des PredicateBuilder-Objekts kann angegeben werden, ob die Abfrage true oder false zurückgeben soll. Danach kann man die einzelnen Elemente zusammenfügen.

Hier ein Beispiel einer oder-Verknüpfung:

var innerPredicate = PredicateBuilder.True<SearchResult>>();

innerPredicate = innerPredicate.Or(item => item.Content == query);
innerPredicate = innerPredicate.Or(item => item.ContentElements == query);

Nun ist es aber so, dass man diese oft auch mit und-Verknüpfungen  verbinden will. Dies ist möglich, indem man verschiedene Predicates verwendet. Das obere Beispiel wird erweitert, indem jedes Wort des Querys im Content und im ContentElement gesucht wird. Dabei sollen nur Items zurückgegeben werden, welche alle Wörter enthalten:

var predicate = PredicateBuilder.True<SearchResult>();

char[] separators = new[] { ' ', ',', '-', ':', '\t' };
List<string> wordList = query.Split(separators).ToList();

foreach (string word in wordList)
{
    string searchTerm = word;
    var innerPredicate = PredicateBuilder.True<SearchResult>();

    innerPredicate = innerPredicate.Or(item => item.Content == searchTerm);
    innerPredicate = innerPredicate.Or(item => item.ContentElements == searchTerm);
    predicate = predicate.And(innerPredicate);
}

Damit können wir in unserem LINQ-Query .Where( result => result.Content == query) ganz einfach durch .Where(predicate) ersetzen und haben schon eine komplexe Abfrage erstellt.

Um datumsabhängige Items (z.B. News oder Events) zu finden, stellt Sitecore eine Erweiterung des DateTimes-Objekts zur Verfügung: die Between-Methode. Diese verlangt 3 Argumente (untere Grenze, obere Grenze, Zugehörigkeit der Grenzen) und kann ebenfalls einem Predicate angehängt werden:

predicate.Or(item => item.TemplateId != newsTemplateId);
predicate.Or(item => item.Date.Between(DateTime.MinValue, DateTime.Now, Inclusion.Upper));

Das obige Beispiel trifft immer dann zu, wenn das Item keine News ist. Dies ist wichtig, da wir hier nicht auf die Gültigkeit des Datums überprüfen wollen. Wenn das Item aber eine News ist, muss deren Datum vor dem Jetzigen sein muss. Sprich: Es muss zwischen dem minimalem Datum (1.1.1) und jetzt sein. Inclusion.Upper bedeutet, dass auch News von heute berücksichtigt werden sollen.

Categories  Best Practice Tags  Lucene  Search  Sitecore 7

No comments

Add your comment

Your email address will not be published. Required fields are marked *

*