April 2019

Band 34, Nummer 4

[.NET]

Implementieren Ihrer eigenen Unternehmenssuche

Von Xavier Morera

Sie nehmen die Suchfunktion wahrscheinlich als selbstverständlich hin. Sie verwenden sie täglich, um alle möglichen Aufgaben zu erledigen, von der Suche nach einem Zimmer für Ihre nächste Reise bis hin zur Suche nach Informationen, die Sie für Ihre Arbeit benötigen. Eine ordnungsgemäß implementierte Suchfunktion kann Ihnen helfen, Geld zu sparen – oder Geld zu verdienen. Und selbst in einer großartigen App führt eine schlechte Suchfunktion immer noch zu einer unbefriedigenden UX.

Das Problem ist, dass die Suche trotz ihrer Wichtigkeit eine der am meisten missverstandenen Funktionen in der IT ist, die nur dann bemerkt wird, wenn sie fehlt oder nicht funktioniert. Aber die Suche muss keine undurchsichtige Funktion sein, die schwer zu implementieren ist. In diesem Artikel werde ich erläutern, wie Sie eine Unternehmenssuch-API in C# entwickeln können.

Um mehr über die Suche zu erfahren, benötigen Sie eine Suchmaschine. Es gibt viele Optionen, von Open-Source- bis hin zu kommerziellen Lösungen sowie Kombinationen aus diesen Möglichkeiten, von denen viele intern Lucene nutzen – die Bibliothek der Wahl für den Informationsabruf. Dies schließt Azure Search, ElasticSearch und Solr ein. Heute verwende ich Solr. Warum ist das so? Diese Bibliothek gibt es schon seit langem, sie ist gut dokumentiert, hat eine lebendige Community sowie viele bemerkenswerte Benutzer, und ich habe sie persönlich bereits genutzt, um eine Suchfunktion in zahlreichen Anwendungen zu implementieren, von kleinen Websites bis hin zu großen Unternehmen.

Abrufen von Solr

Die Installation einer Suchmaschine hört sich nach einer komplizierten Aufgabe an und ist es auch in der Tat, wenn Sie eine Solr-Produktionsinstanz einrichten, die eine hohe Anzahl von Abfragen pro Sekunde (Queries Per Second, QPS) unterstützen muss. (QPS ist eine gängige Metrik, die verwendet wird, um sich auf die Suchworkload zu beziehen.)

Diese Schritte werden ausführlich im Abschnitt „Installing Solr“ (Installation von Solr) der Solr-Dokumentation (lucene.apache.org/solr) sowie im Abschnitt „The Well-Configured Solr Instance“ (Die wohlkonfigurierte Solr-Instanz) im „Apache Solr Reference Guide“ (Apache Solr-Referenzhandbuch, bit.ly/2IK7mqY) behandelt. Aber ich benötige nur eine Solr-Entwicklungsinstanz, also navigiere ich einfach zu bit.ly/2tEXqoo und rufe ein binäres Release ab. „solr-7.7.0.zip“ eignet sich dafür.

Ich lade die ZIP-Datei herunter, entpacke sie, öffne die Befehlszeile und ändere das Verzeichnis in den Stamm des Ordners, in dem ich die Datei entpackt habe. Dann gebe ich den folgenden Befehl aus:

> bin\solr.cmd start

Das ist alles. Ich muss nur zu http://localhost:8983 navigieren. Dort werde ich mit der Solr-Administratorbenutzeroberfläche begrüßt, die in Abbildung 1 gezeigt wird.

Eine aktive Suchmaschine, die ausgeführt wird
Abbildung 1: Eine aktive Suchmaschine, die ausgeführt wird

Abrufen von Daten

Nun benötige ich Daten. Es sind zahlreiche Datasets mit interessanten Daten verfügbar, aber jeden Tag landen Tausende von Entwicklern bei StackOverflow, weil sie sich nicht genau erinnern können, wie in eine Datei geschrieben wird, weil sie VIM nicht beenden können oder weil sie einen C#-Codeausschnitt benötigen, der ein bestimmtes Problem löst. Die gute Nachricht ist, dass die StackOverflow-Daten als XML-Dump mit etwa 10 Millionen Fragen und Antworten, Tags, Badges, anonymisierten Benutzerinformationen und mehr verfügbar sind (bit.ly/1GsHll6).

Noch besser ist, dass ich ein Dataset mit dem gleichen Format aus einer kleineren StackExchange-Website mit nur wenigen tausend Fragen auswählen kann. Ich kann zuerst mit dem kleineren Dataset testen und später die Infrastruktur aufrüsten, um mit mehr Daten zu arbeiten.

Ich beginne mit den Daten von datascience.stackexchange.com, die etwa 25.000 Beiträge enthalten. Die Datei heißt „datascience.stackexchange.com.7z“. Ich lade sie herunter und extrahiere „Posts.xml“.

Einige erforderliche Konzepte

Ich verfüge über eine Suchmaschine und Daten, also ist dies ein guter Zeitpunkt, um einige der wichtigen Konzepte vorzustellen. Wenn Sie es gewohnt sind, mit relationalen Datenbanken zu arbeiten, werden sie Ihnen wahrscheinlich vertraut sein.

Der Index ist der Ort, an dem eine Suchmaschine alle Daten speichert, die für die Suche erfasst werden. Auf einer hohen Ebene speichert Solr Daten in einem so genannten invertierten Index. In einem invertierten Index verweisen Wörter (oder Token) auf bestimmte Dokumente. Wenn Sie nach einem bestimmten Wort suchen, handelt es sich um eine Abfrage. Wenn das Wort gefunden wird (ein Treffer oder eine Übereinstimmung), zeigt der Index an, welche Dokumente dieses Wort enthalten und wo sie sich befinden.

Mit dem Schema geben Sie die Struktur der Daten an. Jeder Datensatz wird als Dokument bezeichnet, und genau wie Sie die Spalten einer Datenbank definieren, geben Sie die Felder eines Dokuments in einer Suchmaschine an.

Sie erstellen das Schema, indem Sie eine XML-Datei namens „schema.xml“ bearbeiten und die Felder mit ihren Typen definieren. Sie können aber auch dynamische Felder verwenden, bei denen beim Hinzufügen eines unbekannten Felds automatisch ein neues Feld erstellt wird. Dies kann sinnvoll sein, wenn Sie sich nicht ganz sicher sind, welche Felder in den Daten vorhanden sind.

Es ist Ihnen vielleicht schon aufgefallen, dass Solr einem NoSQL-Dokumentenspeicher sehr ähnlich ist, da Solr Dokumente mit Feldern enthalten kann, die denormalisiert und nicht unbedingt konsistent über die gesamte Dokumentsammlung hinweg sind.

Der schemalose Modus ist eine weitere Möglichkeit, die Daten innerhalb des Index zu modellieren. In diesem Fall informieren Sie Solr nicht explizit darüber, welche Felder indiziert werden. Stattdessen fügen Sie einfach Daten hinzu, und Solr erstellt ein Schema, das auf dem Typ der Daten basiert, die dem Index hinzugefügt werden. Um den schemalosen Modus verwenden zu können, müssen Sie über ein verwaltetes Schema verfügen, d.h. Sie können Felder nicht manuell bearbeiten. Dies ist besonders nützlich für eine Datenexplorationsphase oder einen Proof of Concept.

Ich werde ein manuell bearbeitetes Schema verwenden. Dies ist der für die Produktion empfohlene Ansatz, da eine bessere Kontrolle ermöglicht wird.

Die Indizierung ist der Vorgang des Hinzufügens von Daten zum Index. Während der Indizierung liest eine Anwendung Daten aus verschiedenen Quellen und bereitet diese Daten für die Erfassung vor. „Indizierung“ ist das Wort, das für das Hinzufügen von Dokumenten verwendet wird. Manchmal wird auch der Begriff „Einspeisen“ verwendet.

Sobald die Dokumente indiziert sind, steht die Suchfunktion zur Verfügung, die hoffentlich die für eine bestimmte Suche relevantesten Dokumente an den ersten Positionen zurückgibt. 

Durch die Relevanzbewertung werden die am besten geeigneten Ergebnisse an oberster Stelle zurückgegeben. Dies ist ein leicht zu erklärendes Konzept. Im besten Fall führen Sie eine Abfrage aus, und die Suchmaschine „liest“ Ihre Gedanken und gibt genau die Dokumente zurück, nach denen Sie gesucht haben.

Genauigkeit und Trefferquote: Genauigkeit bezieht sich darauf, wie viele Ihrer Ergebnisse relevant sind (die Qualität Ihrer Ergebnisse), während Trefferquote bedeutet, wie viele der insgesamt zurückgegebenen Ergebnisse relevant sind (die Menge oder Vollständigkeit der Ergebnisse). 

Analysetools, Tokenizer und Filter: Wenn Daten indiziert oder durchsucht werden, untersucht ein Analysetool den Text und generiert einen Tokenstream. Transformationen werden dann über Filter angewendet, um zu versuchen, Abfragen mit den indizierten Daten abzugleichen. Sie müssen diese verstehen, wenn Sie einen tieferen Einblick in die Suchmaschinentechnologie gewinnen möchten. Glücklicherweise können Sie mit der Erstellung einer Anwendung auf einer hohen Übersichtsebene beginnen.

Verstehen der Daten

Ich muss die Daten verstehen, um den Index modellieren zu können. Dazu öffne ich „Posts.xml“ und analysiere die Daten. 

So sehen die Daten aus. Der posts-Knoten enthält viele untergeordnete row-Knoten. Jeder untergeordnete Knoten entspricht einem Datensatz, wobei jedes Feld als Attribut in jedem untergeordneten Knoten exportiert wird. Die Daten sind ziemlich bereinigt und eignen sich daher gut für die Erfassung in der Suchmaschine:

<posts>
  <row Id=”5” PostTypeId=”1” CreationDate=”2014-05-13T23:58:30.457”
    Score=”9” ViewCount=”448” Body=”<Contains the body of the question or answer>”
      OwnerUserId=”5” LastActivityDate=”2014-05-14T00:36:31.077”
        Title=”How can I do simple machine learning without hard-coding behavior?”
          Tags=”<machine-learning,artificial-intelligence>;” AnswerCount=”4”
            CommentCount=”5” FavoriteCount=”1”
              ClosedDate=”2014-05-14T14:40:25.950” />
  ...
</posts>

Auf einen Blick kann ich schnell erkennen, dass es Felder unterschiedlichen Typs gibt. Es gibt einen eindeutigen Bezeichner, einige Datumsangaben, einige numerische Felder, einige lange und einige kurze Textfelder sowie einige Metadatenfelder. Aus Gründen der Übersichtlichkeit habe ich den Textkörper entfernt, der ein großes Textfeld ist, aber alle anderen Felder liegen in ihrem Originalzustand vor.

Konfigurieren von Solr für die Verwendung eines klassischen Schemas

Es gibt mehrere Möglichkeiten, die Struktur der Daten für den Index anzugeben. Standardmäßig verwendet Solr ein verwaltetes Schema, was bedeutet, dass der schemalose Modus zum Einsatz kommt. Aber ich möchte das Schema manuell erstellen (als „klassisches Schema“ bezeichnet), also muss ich einige Konfigurationsänderungen vornehmen. Zunächst erstelle ich einen Ordner, in dem meine Indexkonfiguration gespeichert wird, und nenne ihn „msdnarticledemo“. Der Ordner befindet sich in „<solr>\server\solr\“, wobei „<solr>“ der Ordner ist, in dem ich Solr entpackt habe.

Nun erstelle ich eine Textdatei namens „core.properties“ im Stamm dieses Ordners, der nur die folgende Zeile hinzugefügt werden muss: name=msdnarticledemo. Diese Datei wird verwendet, um einen Solr-Kern zu erstellen, der nur eine ausgeführte Instanz eines Lucene-Index ist. Häufig wird auch das Wort „Sammlung“ verwendet, das je nach Kontext eine unterschiedliche Bedeutung haben kann. Für meine aktuelle Absicht und meine aktuellen Zwecke ist ein Kern das Äquivalent eines Index.

Ich muss nun den Inhalt eines bereinigten Beispielindex kopieren, um ihn als Basis zu verwenden. Solr enthält einen solchen in „<solr>\server\solr\configsets\_default“. Ich kopiere den Ordner „conf“ in „msdnarticledemo“.

Im sehr wichtigen nächsten Schritt informiere ich Solr, dass ich ein klassisches Schema verwenden möchte, d.h. dass ich mein Schema manuell bearbeiten werde. Dazu öffne ich „solrconfig.xml“ und füge die folgende Zeile hinzu:

<schemaFactory class=”ClassicIndexSchemaFactory”/>

Zusätzlich kommentiere ich (noch in dieser Datei) zwei Knoten aus, die updateRequestProcessorChain:

name=”add-unknown-fields-to-the-schema”
and updateProcessor with:
name=”add-schema-fields”

Diese beiden Funktionen ermöglichen es Solr, neue Felder hinzuzufügen, während die Daten im schemalosen Modus indiziert werden. Ich entferne auch den Kommentar innerhalb dieses XML-Knotens, da „--“ innerhalb von XML-Kommentaren unzulässig ist.

Schließlich benenne ich das verwaltete Schema in „schema.xml“ um. Somit ist Solr bereit, ein manuell erstelltes Schema zu verwenden.

Erstellen des Schemas

Der nächste Schritt besteht darin, die Felder im Schema zu definieren, also öffne ich „schema.xml“ und scrolle nach unten, bis ich die Definition von id, _text_ und _root_ finde.

So wird jedes Feld als ein <field>-XML-Knoten definiert, der Folgendes enthält:

  • name: Der Name für jedes Feld.
  • type: Der Typ des Felds. Sie können in der Datei „schema.xml“ ändern, wie jeder Typ verarbeitet wird.
  • indexed: TRUE gibt an, dass dieses Feld für die Suche verwendet werden kann.
  • stored: TRUE gibt an, dass dieses Feld für die Anzeige zurückgegeben werden kann.
  • required: TRUE gibt an, dieses Feld indiziert werden muss, andernfalls wird ein Fehler ausgelöst.
  • multiValued: TRUE gibt an, dass dieses Feld mehrere Werte enthalten kann.

Dies mag zunächst verwirrend sein, aber einige Felder können angezeigt, aber nicht durchsucht werden, und andere können durchsucht werden, aber nach der Indizierung möglicherweise nicht abgerufen werden. Es gibt noch andere erweiterte Attribute, aber ich werde an dieser Stelle nicht auf ihre Details eingehen.

Abbildung 2 zeigt, wie ich die Felder im Schema für Beiträge definiere. Für Texttypen verwende ich verschiedene Typen, darunter string, text_get_sort und text_general. Die Textsuche ist eine Hauptaufgabe der Suche, daher die verschiedenen textfähigen Typen. Ich verwende auch eine Datumsangabe, eine ganze Zahl, einen Gleitkommawert und ein Feld, das mehrere Werte enthält: Tags.

Abbildung 2: Felder in der Datei „Schema.xml“

<field name=”id” type=”string” indexed=”true” stored=”true”
  required=”true” multiValued=”false” />
<field name=”postTypeId” type=”pint” indexed=”true” stored=”true” />
<field name=”title” type=”text_gen_sort” indexed=”true”
  stored=”true” multiValued=”false”/
<field name=”body” type=”text_general” indexed=”false”
  stored=”true” multiValued=”false”/>
<field name=”tags” type=”string” indexed=”true” stored=”true”
  multiValued=”true”/>
<field name=”postScore” type=”pfloat” indexed=”true” stored=”true”/>
<field name=”ownerUserId” type=”pint” indexed=”true” stored=”true” />
<field name=”answerCount” type=”pint” indexed=”true” stored=”true” />
<field name=”commentCount” type=”pint” indexed=”true” stored=”true” />
<field name=”favoriteCount” type=”pint” indexed=”true” stored=”true” />
<field name=”viewCount” type=”pint” indexed=”true” stored=”true” />                
<field name=”creationDate” type=”pdate” indexed=”true” stored=”true” />
<field name=”lastActivityDate” type=”pdate” indexed=”true” stored=”true” />
<field name=”closedDate” type=”pdate” indexed=”true” stored=”true” />

Die nächsten Schritte können je nach Implementierung variieren. Im Moment habe ich viele Felder und kann angeben, in welchem Feld ich suchen möchte und wie wichtig dieses Feld im Vergleich zu den anderen Feldern ist.

Aber zu Beginn kann ich das alle Daten erfassende Feld _text_ verwenden, um eine Suche über alle meine Felder hinweg durchzuführen. Ich erstelle einfach ein copyField und informiere Solr, dass die Daten aus allen Feldern in mein Standardfeld kopiert werden sollen:

<copyField source=”*” dest=”_text_”/>

Wenn ich nun eine Suche ausführe, sucht Solr in diesem Feld und gibt jedes Dokument zurück, das meiner Abfrage entspricht.

Als nächstes starte ich Solr neu, um den Kern zu laden und die Änderungen anzuwenden, indem ich diesen Befehl ausführe:

> bin\solr.cmd restart

Ich bin nun bereit, mit der Erstellung einer C#-Anwendung zu beginnen.                 

Abrufen von SolrNet und Modellieren der Daten

Solr bietet eine REST-ähnliche API, die Sie problemlos aus jeder Anwendung heraus verwenden können. Und es kommt noch besser: Es gibt eine Bibliothek namens SolrNet (bit.ly/2XwkROA), die eine Abstraktion von Solr bereitstellt, sodass Sie problemlos mit stark typisierten Objekten und vielen Funktionen arbeiten können, um die Entwicklung von Suchanwendungen zu beschleunigen.

Der einfachste Weg, SolrNet abzurufen, ist die Installation des SolrNet-Pakets von NuGet. Ich werde die Bibliothek in die neue Konsolenanwendung aufnehmen, die ich mit Visual Studio 2017 erstellt habe. Möglicherweise möchten Sie auch zusätzliche Pakete (z.B. SolrCloud) herunterladen, die für die Verwendung anderer Invertierungskontrollmechanismen und für zusätzliche Funktionen erforderlich sind.

In meiner Konsolenanwendung muss ich die Daten im Index modellieren. Das ist recht unkompliziert: Ich erstelle einfach eine neue Klassendatei namens „Post.cs“, wie in Abbildung 3 gezeigt.

Abbildung 3: Post-Dokumentmodell

class Post
{
  [SolrUniqueKey(“id”)]
  public string Id { get; set; }
  [SolrField(“postTypeId”)]
  public int PostTypeId { get; set; }
  [SolrField(“title”)]
  public string Title { get; set; }
  [SolrField(“body”)]
  public string Body { get; set; }
  [SolrField(“tags”)]
  public ICollection<string> Tags { get; set; } = new List<string>();
  [SolrField(“postScore”)]
  public float PostScore { get; set; }
  [SolrField(“ownerUserId”)]
  public int? OwnerUserId { get; set; }
  [SolrField(“answerCount”)]
  public int? AnswerCount { get; set; }
  [SolrField(“commentCount”)]
  public int CommentCount { get; set; }
  [SolrField(“favoriteCount”)]
  public int? FavoriteCount { get; set; }
  [SolrField(“viewCount”)]
  public int? ViewCount { get; set; }
  [SolrField(“creationDate”)]
  public DateTime CreationDate { get; set; }
  [SolrField(“lastActivityDate”)]
  public DateTime LastActivityDate { get; set; }
  [SolrField(“closedDate”)]
  public DateTime? ClosedDate { get; set; }
}

Dies ist nichts anderes als ein einfaches altes CLR-Objekt (POCO), das jedes einzelne Dokument in meinem Index darstellt, aber mit einem Attribut, das SolrNet informiert, welchem Feld jede Eigenschaft zugeordnet ist.

Erstellen einer Suchanwendung

Wenn Sie eine Suchanwendung erstellen, erstellen Sie in der Regel zwei separate Funktionen:

Den Indexer: Dies ist die Anwendung, die ich zuerst erstellen muss. Um Daten zum Durchsuchen zu haben, muss ich diese Daten ganz einfach in Solr einlesen. Dies kann das Einlesen der Daten aus verschiedenen Quellen, die Konvertierung aus verschiedenen Formaten und mehr beinhalten, bis die Daten schließlich durchsucht werden können.

Die Suchanwendung: Sobald ich in meinem Index über Daten verfüge, kann ich damit beginnen, an der Suchanwendung zu arbeiten.

Bei beiden Komponenten erfordert der erste Schritt die Initialisierung von SolrNet, was Sie mit der folgenden Zeile in der Konsolenanwendung erreichen können (stellen Sie sicher, dass Solr ausgeführt wird!):

Startup.Init<Post>(“http://localhost:8983/solr/msdnarticledemo”);

Ich erstelle eine Klasse für jede Funktion in meiner Anwendung.

Erstellen des Indexers

Um die Dokumente zu indizieren, rufe ich zunächst die SolrNet-Dienstinstanz ab, die es mir ermöglicht, alle unterstützten Vorgänge zu starten:

var solr = ServiceLocator.Current.GetInstance<ISolrOperations<Post>>();

Als nächstes muss ich den Inhalt von „Posts.xml“ in ein XMLDocument einlesen, was das Iterieren über jeden Knoten, das Erstellen eines neuen Post-Objekts, das Extrahieren jedes Attributs aus dem XMLNode und das Zuweisen zu der entsprechenden Eigenschaft beinhaltet.

Beachten Sie, dass im Bereich des Informationsabrufs oder der Suche Daten denormalisiert gespeichert werden. Im Gegensatz dazu normalisieren Sie bei der Arbeit mit Datenbanken typischerweise Daten, um Duplizierungen zu vermeiden. Anstatt den Namen des Erstellers zu einem Beitrag hinzuzufügen, fügen Sie eine Ganzzahl-ID hinzu und erstellen eine separate Tabelle, um die ID mit einem Namen abzugleichen. Bei der Suche fügen Sie den Namen jedoch als Teil des Beitrags hinzu und duplizieren so Daten. Warum ist das so? Wenn Daten normalisiert werden, müssen Sie Joins für den Abruf durchführen, die recht rechenintensiv sind. Aber eines der Hauptziele einer Suchmaschine ist Geschwindigkeit. Benutzer erwarten, dass sie eine Schaltfläche drücken und sofort die gewünschten Ergebnisse erhalten.

Doch nun zurück zum Erstellen des Post-Objekts. In Abbildung 4 zeige ich nur drei Felder, da das Hinzufügen weiterer Felder recht einfach ist. Beachten Sie, dass Tags mehrwertig sind und dass ich nach NULL-Werten suche, um Ausnahmen zu vermeiden.  

Abbildung 4: Auffüllen der Felder mit Daten

Post post = new Post();
post.Id = node.Attributes[“Id”].Value;
if (node.Attributes[“Title”] != null)
{
  post.Title = node.Attributes[“Title”].Value;
}
if (node.Attributes[“Tags”] != null){
  post.Tags = node.Attributes[“Tags”].Value.Split(new char[] { ‘<’, ‘>’ })
    .Where(t => !string.IsNullOrEmpty(t)).ToList();}
// Add all other fields

Sobald ich das Objekt mit Daten aufgefüllt habe, kann ich jede Instanz mit der Add-Methode hinzufügen:

solr.Add(post);

Alternativ kann ich auch eine Beitragssammlung erstellen und Beiträge in Batches mit AddRange hinzufügen:

solr.AddRange(post_list);

Beide Ansätze sind in Ordnung, aber es wurde in vielen Produktionsbereitstellungen beobachtet, dass das Hinzufügen von Dokumenten in Batches von 100 Stück tendenziell zu einer besseren Leistung führt. Beachten Sie, dass das Hinzufügen eines Dokuments dieses nicht durchsuchbar macht. Ich muss committen:

solr.Commit();

Jetzt erfolgt die Ausführung, und je nach der Menge der zu indizierenden Daten und dem Computer, auf dem die Indizierung ausgeführt wird, kann dieser Vorgang einige Sekunden bis hin zu einigen Minuten dauern.

Sobald der Vorgang abgeschlossen ist, kann ich zur Solr-Administratorbenutzeroberfläche navigieren, auf der linken Seite in der Mitte nach einem Dropdownmenü suchen, das „Core Selector“ (Kernauswahl) heißt, und meinen Kern auswählen („msdnarticledemo“). Auf der Registerkarte „Overview“ (Übersicht) kann ich die Statistik anzeigen, die mich darüber informiert, wie viele Dokumente ich soeben indiziert habe.

In meiner Datensicherung hatte ich 25.488 Beiträge. Dieser Wert stimmt mit dem angezeigten Wert überein:

Statistics
  Last Modified: less than a minute ago
  Num Docs:25488
  Max Doc:25688

Nachdem ich nun über Daten in meinem Index verfüge, bin ich bereit, mit der Arbeit an der Suchseite zu beginnen.

Suchen in Solr

Bevor ich mich wieder Visual Studio widme, möchte ich eine schnelle Suche in der Solr-Administratorbenutzeroberfläche zeigen und einige der verfügbaren Parameter erläutern.

Im msdnarticledemo-Kern klicke ich auf „Query“ (Abfragen) und drücke die blaue Schaltfläche „Execute Query“ (Abfrage ausführen) am unteren Rand. Ich erhalte alle meine Dokumente im JSON-Format, wie in Abbildung 5 gezeigt.

Eine Abfrage in Solr über die Administratorbenutzeroberfläche
Abbildung 5A: Eine Abfrage in Solr über die Administratorbenutzeroberfläche

Also, was genau habe ich gerade gemacht und warum habe ich alle Dokumente im Index erhalten? Die Antwort ist einfach. Sehen Sie sich die Parameter an, die Spalte, die mit „Request-Handler (qt)“ beginnt. Wie Sie sehen können, ist ein Parameter mit „q“ bezeichnet und weist den Wert *:* auf.  Dies hat bewirkt, dass alle Dokumente angezeigt wurden. Tatsächlich lautete die von mir ausgeführte Abfrage, alle Felder nach allen Werten mithilfe eines Schlüssel-Wert-Paars zu durchsuchen. Wenn ich stattdessen nur nach „Solr“ im Titel suchen wollte, dann wäre der q-Wert „Title:Solr“. 

Dies ist sehr nützlich für die Erstellung komplizierterer Abfragen, die für jedes Feld unterschiedliche Gewichtungen liefern, was sinnvoll ist. Ein Wort oder ein Ausdruck, das bzw. der in einem Titel gefunden wird, ist wichtiger als eine Formulierung im Inhalt. Wenn ein Dokument beispielsweise „Unternehmenssuche“ im Titel enthält, ist es sehr wahrscheinlich, dass sich der Inhalt des gesamten Dokuments mit Unternehmenssuche beschäftigen wird. Aber wenn ich diesen Ausdruck in irgendeinem Teil des Textkörpers eines Dokuments finde, kann es sich möglicherweise nur um einen Hinweis auf etwas handeln, das kaum in Zusammenhang damit steht.

Der q-Parameter ist wahrscheinlich der wichtigste, da er Dokumente in der Reihenfolge ihrer Relevanz durch die Berechnung einer Bewertung abruft. Es gibt jedoch eine Reihe weiterer Parameter, die Sie über den Anforderungshandler verwenden können, um die Verarbeitung von Anforderungen durch Solr zu konfigurieren, darunter Filterabfragen (fq), Sortierung, Feldliste (fl) und vieles mehr, das Sie in der Solr-Dokumentation unter bit.ly/2GVmYGl finden können. Mit diesen Parametern können Sie beginnen, komplexere Abfragen zu erstellen. Es braucht einige Zeit, um diese Herausforderung zu meistern, aber je mehr Sie lernen, desto bessere Relevanzbewertungen werden Sie erhalten.

Denken Sie daran, dass die Administratorbenutzeroberfläche nicht die Funktionsweise einer Anwendung mit Solr darstellt. Zu diesem Zweck wird die REST-ähnliche Schnittstelle verwendet. Direkt über den Ergebnissen befindet sich ein Link in einem grauen Feld, der den Aufruf für diese spezielle Abfrage enthält. Wenn Sie darauf klicken, wird ein neues Fenster geöffnet, das die Antwort enthält.

Dies ist meine Abfrage:

http://localhost:8983/solr/msdnarticledemo/select?q=*%3A*&wt=json

SolrNet führt Aufrufe wie diese im Hintergrund aus, stellt aber Objekte bereit, die ich aus meiner .NET-Anwendung verwenden kann. Nun erstelle ich eine einfachen Suchanwendung.

Erstellen der Suchanwendung

In diesem speziellen Fall durchsuche ich die Fragen in meinem Dataset in allen Feldern. Angesichts der Tatsache, dass die Daten sowohl Fragen als auch Antworten enthalten, filtere ich nach der PostTypeId mit „1“, was bedeutet, dass es sich um eine Frage handelt.  Zu diesem Zweck verwende ich eine Filterabfrage: den fq-Parameter.

Außerdem lege ich einige Abfrageoptionen fest, um jeweils eine Seite mit Ergebnissen zurückzugeben: nämlich „Rows“, um die Anzahl der Ergebnisse anzugeben, und StartOrCursor, um den Offset (Start) anzugeben. Außerdem lege ich natürlich die Abfrage fest.

Abbildung 6 zeigt den Code, der für die Ausführung einer einfachen Suche erforderlich ist, wobei „query“ der Text ist, nach dem ich suche.

Abbildung 6: Ausführen einer einfachen Suche

QueryOptions query_options = new QueryOptions
{
  Rows = 10,
  StartOrCursor = new StartOrCursor.Start(0),
  FilterQueries = new ISolrQuery[] {
    new SolrQueryByField(“postTypeId”, “1”),
    }
};
// Construct the query
SolrQuery query = new SolrQuery(keywords);
// Run a basic keyword search, filtering for questions only
var posts = solr.Query(query, query_options);

Nach Ausführung der Abfrage erhalte ich „posts“, ein SolrQueryResults-Objekt. Es enthält eine Sammlung von Ergebnissen sowie zahlreiche Eigenschaften mit mehreren Objekten, die zusätzliche Funktionen bieten. Da ich nun über diese Ergebnisse verfüge, kann ich sie dem Benutzer anzeigen.

Eingrenzen der Ergebnisse

In vielen Fällen können die ursprünglichen Ergebnisse gut sein, aber der Benutzer möchte sie ggf. durch ein bestimmtes Metadatenfeld eingrenzen. Es ist möglich, mit Hilfe von Facets einen Drilldown nach einem Feld auszuführen. In einem Facet erhalte ich eine Liste von Schlüssel-Wert-Paaren. Beispielsweise erhalte ich mit Tags jedes Tag und die Häufigkeit, mit der jedes Tag auftritt. Facets werden häufig mit numerischen, Datums- oder Zeichenfolgenfeldern verwendet. Textfelder sind knifflig.

Um Facets zu aktivieren, muss ich eine neue QueryOption „Facet“ hinzufügen:  

Facet = new FacetParameters
{
  Queries = new[] {
    new SolrFacetFieldQuery(“tags”)
  }
}

Jetzt kann ich meine Facets aus posts.FacetFields["tags"] abrufen, einer Sammlung von Schlüssel-Wert-Paaren, die jedes einzelne Tag und die Häufigkeit des Vorkommens jedes Tags in der Ergebnismenge enthalten.

Dann kann ich dem Benutzer die Auswahl der zu durchsuchenden Tags ermöglichen, die Anzahl der Ergebnisse mit einer Filterabfrage verringern und idealerweise relevante Dokumente zurückgeben. 

Verbessern Ihrer Suchfunktion – was kommt als nächstes?

Bisher habe ich die Grundlagen der Implementierung der einfachen Suche in C# mit Solr und SolrNet anhand der Fragen von einer der StackExchange-Websites erläutert. Dies ist jedoch nur der Anfang einer neuen Reise, auf der ich mich mit der Kunst beschäftigen kann, relevante Ergebnisse mit Solr zurückzugeben.

Einige der nächsten Schritte sind die Suche nach einzelnen Feldern mit unterschiedlichen Gewichtungen, die Hervorhebung in den Ergebnissen, um Übereinstimmungen im Inhalt anzuzeigen, die Anwendung von Synonymen, um Ergebnisse zu erhalten, die verwandt sind, aber möglicherweise nicht genau die gesuchten Wörter enthalten, die Stammformbildung, die Wörter auf ihre Basis reduziert, um die Trefferquote zu erhöhen, die phonetische Suche, die internationalen Benutzern hilft, und vieles mehr.

Alles in allem ist das Erlernen der Implementierung einer Suchfunktion eine wertvolle Fähigkeit, die in Ihrer Zukunft als Entwickler wertvolle Früchte tragen kann.

Als Ergänzung zu diesem Artikel habe ich ein einfaches Suchprojekt erstellt (wie in Abbildung 7 gezeigt), das Sie herunterladen können, um mehr über die Unternehmenssuche zu erfahren. Viel Spaß beim Suchen!

Ein Beispielprojekt für die Suche
Abbildung 7A: Ein Beispielprojekt für die Suche


Xavier Morerahilft Entwicklern, die Unternehmenssuche und Big Data zu verstehen. Er erstellt Kurse auf Pluralsight und manchmal auf Cloudera. Er hat viele Jahre bei Search Technologies (heute zu Accenture gehörig) gearbeitet und sich mit Suchimplementierungen beschäftigt. Er lebt in Costa Rica, und Sie finden ihn unter xaviermorera.com.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Jose Arias (Accenture), Jonathan Gonzalez (Accenture)
Jose Arias beschäftigt sich mit Begeisterung mit Such- und Big Data-bezogenen Technologien, insbesondere mit solchen, die in der Datenanalyse eingesetzt werden.  Er ist Senior Developer bei Accenture Search & Content Analytics. https://www.linkedin.com/in/joseariasq/
Jonathan Gonzalez <(j.gonzalez.vindas@accenture.com)>
Jonathan Gonzalez ist Senior Managing Architect bei Accenture mit über 18 Jahren Erfahrung in der Softwareentwicklung und im Softwaredesign. Die letzten 13 Jahre hat er sich auf Informationsabruf, Unternehmenssuche, Datenverarbeitung und Inhaltsanalyse spezialisiert. https://www.linkedin.com/in/jonathan-gonzalez-vindas-ba3b1543/