Navigation and service panel


Content

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


Performante Produktsuche Teil 5 - Produkte suchen

By Tobias Studer on 6. June 2013, No comments

Bis jetzt haben wir gesehen, wie wir den Index konfigurieren und abfüllen können, damit wir eine Zuweisung von Produkten und Katalogen mit einer Gewichtung herstellen können. Als nächsten Schritt wird eine Möglichkeit aufgezeigt, wie mit Hilfe dieses Indexes die bevorzugte Url auf eine Produktdetailseite generiert werden kann. Dazu schreiben wir eine Methode, die eine Produkt-ID entgegen nimmt und die Url zurück gibt.

Teil 1 - Übersicht und Vorbereitungen
Teil 2 - Index abfüllen mit dem VirtualProductsCrawler
Teil 3 - Abhängigkeit zwischen Produkten und Katalogen herstellen
Teil 4 - Index Performance optimieren
Teil 5 - Produkte suchen
Teil 6 - Zusammenführung der einzelnen Teile

TL;DR

Um eine performante Verlinkung von Produkten, welchen nicht direkt ein Katalog zugewiesen werden kann, sicherzustellen, bedarf es einige Konfiguration, Anpassungen in der Crawler- und Produkt-Implementation und zusätzlichen Code in der Suchlogik. Diese Mini-Serie beschreibt die einzelnen Schritte, um an dieses Ziel zu gelangen.

Die SearchFacade

public class SearchFacade
{
    public static string GetProductUrl(string id)
    {
        Query query = BuildQuery(id);
        LuceneSearcher searcher = new LuceneSearcher(query);
        SearchResult result = searcher.Search(1).FirstOrDefault();

        return result != null ? result.Url : string.Empty;
    }

    private static Query BuildQuery(string id)
    {
        if (ID.IsID(id))
        {
            id = ID.Parse(id).ToShortID().ToString();
        }

        Assert.IsTrue(ShortID.IsShortID(id), "The id parameter must be a short id.");

        BooleanQuery query = new BooleanQuery();
        query.Add(new BooleanClause(new TermQuery(new Term(Sitecore.Search.BuiltinFields.Language, Sitecore.Context.Language.Name)), BooleanClause.Occur.MUST));
        query.Add(new BooleanClause(new TermQuery(new Term(Sitecore.Search.BuiltinFields.Path, id)), BooleanClause.Occur.MUST));
        
        return query;
    }
}

public class SearchResult { public Item Item { get; set; } public ProductBase Product { get; set; } public string Url { get; set; } }

Die Search(...)-Methode des LuceneSearcher sieht folgendermassen aus.

public virtual IEnumerable<SearchResult> Search(int take = 0)
{
    try
    {
        using (IndexSearchContext context = SearchManager.GetIndex(this.indexName).CreateSearchContext())
        {
            SearchResultController controller = new SearchResultController();
            return controller.GetSearchResult(context.Search(new PreparedQuery(this.query)), take).ToList();
        }
    }
    catch (FileNotFoundException ex)
    {
        Log.Warn("Could not create search context", ex, this);
        return Enumerable.Empty<SearchResult>();
    }
}

Wieso der ToList()-Aufruf innerhalb des using-Blocks wichtig ist, kann hier nachgelesen werden.

Die Suchresultat werden über den IndexSearchContext abgerufen und anschliessend im SearchResultController verarbeitet. Dieser übernimmt die Logik, um den besten Katalog auszuwählen und die Url zu generieren.

public virtual IEnumerable<SearchResult> GetSearchResult(SearchHits searchHits, int take = 0)
{
    // NOTE: The comparer will filter out duplicates of product - catalog combinations
    // that can occur, if the index is not perfectly clean.
    IEnumerable<SearchHit> hits = searchHits.Slice(0)
                                            .Where(hit => hit.Document.GetField(Sitecore.Ecommerce.Search.BuiltinFields.CatalogItemUri) != null)
                                            .Where(hit => hit.Document.GetField(SEARCH_BOOST_INDEX_FIELD) != null)
                                            .Distinct(new SearchHitProductCatalogComparer())
                                            .OrderByDescending(this.GetSearchBoost)
                                            .ToList();

    // NOTE: Calculate the lookup table before filtering the search hits to a max amount
    // to ensure the best ranked catalog for the product will be found
    IDictionary<string, List<string>> lookupTable = this.CalculateLookupTable(hits);

    // NOTE: The lookup table will have the actual amount of found products. The hits need
    // to be filtered again to not have one product multiple times with different catalogs
    // because the best catalog will be taken from the lookup table. This will prevent 
    // duplicate search results.
    return this.GetSearchResults(take, hits.Distinct(new SearchHitProductComparer()), lookupTable);
}

private int GetSearchBoost(SearchHit searchHit)
{
    return NumberUtil.GetValidInteger(this.GetFieldValue(searchHit.Document, AppSettings.SEARCH_BOOST_INDEX_FIELD), int.MinValue);
}

private IDictionary<string, List<string>> CalculateLookupTable(IEnumerable<SearchHit> hits)
{
    Dictionary<string, List<string>> lookupTable = new Dictionary<string, List<string>>();
    foreach (var hit in hits)
    {
        string fieldValue = this.GetFieldValue(hit.Document, Sitecore.Ecommerce.Search.BuiltinFields.CatalogItemUri);
        if (lookupTable.ContainsKey(hit.Url))
        {
            lookupTable[hit.Url].Add(fieldValue);
        }
        else
        {
            lookupTable.Add(hit.Url, new List<string> { fieldValue });
        }
    }

    return lookupTable;
}

private IEnumerable<SearchResult> GetSearchResults(int take, IEnumerable<SearchHit> hits, IDictionary<string, List<string>> lookupTable)
{
    if (take > 0)
    {
        hits = hits.Take(take);
    }

    foreach (SearchHit hit in hits)
    {
        Item catalogItem = this.GetHighestRankedCatalog(lookupTable[hit.Url]);
        Item productItem = this.GetItem(hit.Url, this.productCache);

        if (catalogItem != null && productItem != null)
        {
            yield return new SearchResult
            {
                Url = this.ProductResolver.GetVirtualProductUrl(catalogItem, productItem),
                Item = productItem,
                Product = ProductUtil.GetProduct(productItem)
            };
        }
    }
}

private Item GetHighestRankedCatalog(IEnumerable<string> catalogUris)
{
    return catalogUris
        .Select(uri => Database.GetItem(new ItemUri(uri));)
        .OrderBy(catalog => NumberUtil.GetValidInteger(catalog[RANKING_FIELD]))
        .FirstOrDefault();
}

Zusammenfassung

In diesem Teil haben wir gesehen, wie wir die geleistete Vorarbeit anwenden können, um via Index den Produktlink auf den gewünschten Katalog zu generieren. Im letzten Teil werden wir sehen, wie das ganze Konstrukt in das Product Objekt der SES integriert werden kann.

Hier gehts zum Teil 6 - Zusammenführung der einzelnen Teile.

Categories  E-Commerce Tags  Lucene  Index

No comments

Add your comment

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

*