.NET Framework

Entwickeln einer Unternehmenssuche für .NET

Damian Zapart

Laden Sie die Codebeispiele herunter

Der Aufstieg des Cloud Computings in den letzten Jahren war für Unternehmen ebenso hilfreich wie für Benutzer. Organisationen können ihre Kunden kennenlernen wie nie zuvor und sie personalisiert und zielgerichtet ansprechen. Benutzer können von nahezu überall aus an ihre Daten gelangen, wodurch sie besser zugänglich und nützlicher werden. Weltweit wurden große Rechenzentren aufgebaut, um alle diese Daten zu speichern. Aber Big Data bringt große Herausforderungen mit sich.

Das bekannte Zitat von John Naisbitt "Wir ertrinken in Daten, aber verdursten aus Mangel an Informationen" aus seinem Buch "Megatrends: Ten New Directions Transforming Our Lives” (Warner Books, 1982, dt. Ausgabe unter dem Titel "Megatrends 2000") beschreibt die gegenwärtige Situation im Marktsegment Big Data perfekt. Unternehmen können Petabytes an Daten speichern – diesen Daten Sinnzusammenhänge zu entnehmen und sie durchsuchbar zu machen, ist aber erheblich schwieriger, insbesondere, da die meisten Data Warehouses Daten auf nicht strukturierte Weise (NoSQL) über mehrere Sammlungen in bestimmten Big Data-Speichern oder sogar auf verschiedene Warehouses verteilt speichern. Außerdem gibt es eine Vielzahl von Datenformaten, wie etwa JSON-Dokumente, Microsoft Office-Dateien usw. Das Durchsuchen einer einzelnen unstrukturierten Sammlung stellt normalerweise kein Problem dar, aber es ist viel schwieriger, sämtliche unstrukturierten Daten über mehrere Sammlungen verteilt zu durchsuchen, um nur eine kleine Teilmenge von Ergebnissen zu finden, wenn der Benutzer keine Ahnung hat, wo sich diese Ergebnisse befinden. Hier kommt die Unternehmenssuche ins Spiel.

Unternehmensweite Suche

Und hier zeigt sich die grundlegende Herausforderung der Unternehmenssuche: Wie kann eine große Organisation mit einer großen Anzahl von Datenquellen internen und externen Benutzern die Möglichkeit bieten, alle öffentlich zugänglichen Firmendaten in nur einer Oberfläche zu durchsuchen? Bei dieser einzelnen Oberfläche, also der Benutzerschnittstelle, kann es sich um eine API, eine Firmenwebsite oder sogar nur um ein einfaches Textfeld mit intern implementierter Funktionalität zur automatischen Vervollständigung handeln. Unabhängig davon, für welche Oberfläche sich ein Unternehmen entscheidet, sie muss die Möglichkeit zum Durchsuchen seines gesamten Datenuniversums bieten, das strukturierte ebenso wie unstrukturierte Datenbanken, Intranetdokumente in verschiedenen Formaten, andere APIs und andere Arten von Datenquellen enthalten kann.

Da das Durchsuchen mehrerer Datasets ziemlich komplex ist, gibt es nur ein paar anerkannter Lösungen für die Unternehmenssuche – und die Messlatte liegt hoch. Eine Lösung für Unternehmenssuche muss die folgenden Features enthalten:

  • Inhaltssensitivität: Wissen, wo bestimmte Arten von Daten sich befinden können.
  • Echtzeitindizierung: Alle Daten sind jederzeit indiziert.
  • Inhaltsverarbeitung: Zugriffsmöglichkeit auf verschiedene Datenquellen.

Eine der beliebtesten Lösungen zur Unternehmenssuche ist das quelloffene Elasticsearch (elasticsearch.org). Dieser Java-basierte Server, der auf Apache Lucene (lucene.apache.org) aufbaut, bietet skalierbare Volltextsuche über mehrere Datenquellen hinweg mit JSON-Unterstützung und einer REST-Webschnittstelle sowie hohe Verfügbarkeit, Konfliktmanagement und Echtzeitanalyse. Unter bit.ly/1vzoUrR finden Sie eine Aufstellung aller unterstützten Features.

Von einer hohen Ebene aus betrachtet, stellt sich die Weise, wie Elasticsearch Daten speichert, sehr einfach dar. Das oberste Element der in einem Server bestehenden Struktur wird als Index bezeichnet, und innerhalb eines Datenspeichers kann es mehrere Indizes geben. Der Index ist seinerseits lediglich ein Container für Dokumente (eins oder mehrere), und jedes Dokument stellt eine Sammlung von Feldern dar (ohne definierte Strukturen). Jeder Index kann in als Typen bezeichneten Einheiten zusammengefasste Daten enthalten, die logische Gruppen von Daten innerhalb eines bestimmten Index darstellen.

Es hilft vielleicht, sich Elasticsearch ähnlich einer Tabelle aus der Welt der relationalen Datenbanken vorzustellen. Zwischen den Zeilen und Spalten einer Tabelle und den Dokumenten und Feldern eines Index besteht die gleiche Korrelation, wobei ein Dokument einer Zeile und ein Feld einer Spalte entspricht. Jedoch gibt es in Elasticsearch keine feste Datenstruktur und kein Datenbankschema.

Wie ich schon ausgeführt habe, können Entwickler Daten mit dem Elasticsearch-Server über eine REST-Webschnittstelle austauschen. Das heißt, dass sie Indizes, Typen, Daten oder andere Systeminformationen abfragen können, indem sie einfache REST-Webanforderungen von einem Browser oder einem beliebigen anderen Webclient aus senden. Hier einige Beispiele für GET-Anforderungen:

  • Abfrage auf alle Indizes:
           http://localhost:9200/_cat/indices/?v
  • Abfrage auf Indexmetadaten:
           http://localhost:9200/clients/_stats
  • Abfrage auf alle Indexcaten: 
           http://localhost:9200/clients/_search?q=*:*
  • Abfrage auf einen bestimmten Feldwert im Index: 
           http://localhost:9200/clients/_search?q=field:value
  • Abruf aller Daten innerhalb des Zuordnungstyps des Index:
           http://localhost:9200/clients/orders/_search?q=*:*

Erstellen einer Suche

Für ein Beispiel einer einfachen Lösung mit mehreren Quellen verwende ich Elasticsearch 1.3.4 mit JSON-Dokumenten, PDF-Dokumenten und einer SQL Server-Datenbank. Zunächst beschreibe ich kurz die Installation von Elasticsearch und zeige, wie die Verbindungen mit den einzelnen Datenquellen hergestellt werden, damit die Daten durchsucht werden können. Aus Gründen der Einfachheit wird ein realitätsnahes Szenario vorgestellt, das Datenquellen des wohlbekannten Unternehmens Contoso verwendet.

Ich verwende eine SQL Server 2014-Datenbank, die mehrere Tabellen enthält, von denen ich jedoch nur eine verwende, "dbo.Orders". Wie der Name der Tabelle nahelegt, sind dort Datensätze über die Kundenbestellungen des Unternehmens gespeichert – eine Riesenmenge an Datensätzen, aber leicht zu verwalten:

CREATE TABLE [dbo].[Orders]
(
  [Id] [int] IDENTITY(1,1) NOT NULL primary key,
  [Date] [datetime] NOT NULL,
  [ProductName] [nvarchar](100) NOT NULL,
  [Amount] [int] NOT NULL,
  [UnitPrice] [money] NOT NULL
);

Ferner gibt es eine Netzwerkfreigabe mit mehreren Firmendokumenten, die in einer Ordnerhierarchie strukturiert sind. Diese Dokumente hängen mit verschiedenen Produktmarketingkampagnen zusammen, die das Unternehmen in der Vergangenheit organisiert und in mehreren Dateiformaten, einschließlich PDF und Microsoft Office Word, gespeichert hat. Die durchschnittliche Dokumentgröße ist annähernd 1 MB.

Schließlich gibt es eine firmeninterne API, die die Kundeninformationen des Unternehmens im JSON-Format verfügbar macht; da die Struktur der Antwort bekannt ist, lässt sie sich in ein Objekt vom Typ "Kunde" deserialisieren. Das Ziel ist, sämtliche Datenquellen mithilfe des Elasticsearch-Moduls durchsuchbar zu machen. Ferner soll ein auf der Web API 2 basierender Webdienst aufgebaut werden, der intern mithilfe eines einzelnen Aufrufs an den Elasticsearch-Server eine Unternehmensabfrage über alle Indizes ausführt. (Weitere Informationen zur Web API 2 finden Sie unter bit.ly/1ae6uya.) Der Webdienst gibt die Ergebnisse als Liste von Vorschlägen mit potenziellen Hinweisen auf den Endbenutzer zurück; eine solche Liste kann später von einem in der ASP.NET MVC-Anwendung eingebetteten Steuerelement zum AutoVervollständigen oder von beliebigen anderen Websites genutzt werden.

Einrichtung

Im ersten Schritt muss der Elasticsearch-Server installiert werden. Für Windows kann dies entweder automatisch oder manuell erfolgen und führt zum gleichen Ergebnis – einem laufenden Windows-Dienst, der den Elasticsearch-Server hostet. Die automatische Installation ist sehr schnell und einfach; Sie brauchen lediglich das Elasticsearch-MSI-Installationsprogramm (bit.ly/12RkHDz) herunterzuladen und zu installieren. Leider gibt es keine Möglichkeit, eine Java-Version oder, weitaus wichtiger, eine Elasticsearch-Version auszuwählen. Der manuelle Installationsvorgang macht etwas mehr Mühe, gibt aber viel mehr Kontrolle über die Komponenten, daher ist er für diesen Fall besser geeignet.

Das manuelle Einrichten von Elasticsearch als Windows-Dienst erfordert die folgenden Schritte:

  1. Laden Sie die aktuellste Java SE-Laufzeitumgebung (bit.ly/1m1oKlp) herunter, und installieren Sie sie.
  2. Fügen Sie die Umgebungsvariable mit dem Namen JAVA_HOME hinzu. Ihr Wert muss auf den Ordnerpfad festgelegt werden, unter dem Sie Java installiert haben (z. B. "C:\Programme\Java\jre7"), wie in Abbildung 1 gezeigt.
  3. Laden Sie die Elasticsearch-Datei (bit.ly/1upadla) herunter, und entzippen Sie sie.
  4. Verschieben Sie die entzippten Quellen nach "Programme" | "Elasticsearch" (optional).
  5. Führen Sie die Eingabeaufforderung als Administrator und "service.bat" mit diesem Installationsparameter aus:
           C:\Program Files\Elasticsearch\elasticsearch-1.3.4\bin>service.bat install

Festlegen der Java_Home-Umgebungsvariablen
Abbildung 1 Festlegen der Java_Home-Umgebungsvariablen

Mehr ist nicht erforderlich, um den Windows-Dienst betriebsbereit zu bekommen und den Zugriff auf den Elasticsearch-Server auf "localhost" an Port 9200 zu ermöglichen. Jetzt kann eine Webanforderung an die URL "http://localhost:9200/" über einen beliebigen Webbrowser vorgenommen werden, und es wird eine Antwort in dieser Form zurückgegeben:

{
  "status" : 200,
  "name" : "Washout",
  "version" : {
    "number" : "1.3.4",
    "build_hash" : "a70f3ccb52200f8f2c87e9c370c6597448eb3e45",
    "build_timestamp" : "2014-09-30T09:07:17Z",
    "build_snapshot" : false,
    "lucene_version" : "4.9"
  },
  "tagline" : "You Know, for Search"
}

Jetzt ist die lokale Instanz von Elasticsearch einsatzfähig; allerdings gibt diese rohe Version noch keine Möglichkeit, Verbindungen mit SQL Server herzustellen oder eine Volltextsuche in Datendateien auszuführen. Um diese Features verfügbar zu machen, müssen außerdem verschiedene Plug-Ins installiert werden.

Erweitern von Elasticsearch

Wie bereits erwähnt, ermöglicht die "nackte" Version von Elasticsearch keine Indizierung einer externen Datenquelle, wie SQL Server, Office oder auch nur einer PDF-Datei. Damit alle diese Datenquellen durchsuchbar werden, muss eine Reihe von Plug-Ins installiert werden, was ziemlich einfach ist.

Mein erstes Ziel ist die Unterstützung der Volltextsuche für Anlagen. Unter Anlage verstehe ich hier eine base64-codierte Darstellung der Quelldatei, die als JSON-Dokument auf einen Elasticsearch-Datenspeicher hochgeladen wurde. (Informationen zum Anlagentyp finden Sie unter bit.ly/12RGmvg.) Das für diesen Zweck erforderliche Plug-In ist der Mapper Attachments Type für Elasticsearch-Version 2.3.2, die unter bit.ly/1Alj8sy verfügbar ist. Dies ist die Elasticsearch-Erweiterung, die eine Volltextsuche in Dokumenten ermöglicht, und sie basiert auf dem Apache Tika-Projekt (tika.apache.org), das Metadaten und Textinhalte aus verschiedenen Dokumenttypen erkennt und extrahiert und die unter bit.ly/1qEyVmr aufgelisteten Dateiformate unterstützt.

Wie bei den meisten Plug-Ins für Elasticsearch erfolgt die Installation extrem geradlinig, und es ist lediglich erforderlich, die Eingabeaufforderung als Administrator zu öffnen und den folgenden Befehl auszuführen:

bin>plugin --install elasticsearch/elasticsearch-mapper-attachments/2.3.2

Nach dem Herunterladen und Extrahieren des Plug-Ins muss der Elasticsearch Windows-Dienst neu gestartet werden.

Wenn das geschehen ist, muss die SQL Server-Unterstützung konfiguriert werden. Natürlich gibt es auch dafür ein Plug-In. Es heißt JDBC River (bit.ly/12CK8Zu) und ermöglicht das Abrufen von Daten aus einer JDBC-Quelle, wie etwa SQL Server, zwecks Indizierung in Elasticsearch. Das Plug-In lässt sich leicht installieren und konfigurieren, allerdings verläuft der Installationsvorgang in drei Phasen, die abgeschlossen werden müssen.

  1. Als erstes wird der Microsoft JDBC Driver 4.0 installiert, ein Java-basierter Datenanbieter für SQL Server, der von bit.ly/1maiM2j heruntergeladen werden kann. Hier ist zu beachten, dass die Inhalte der heruntergeladenen Datei in den Ordner "Microsoft JDBC Driver 4.0 for SQL Server" extrahiert werden müssen (der erstellt werden muss, wenn er nicht vorhanden ist), direkt unter dem Ordner "Program Files", sodass sich folgender Pfad ergibt: C:\Program Files\Microsoft JDBC Driver 4.0 for SQL Server.
  2. Als nächstes wird das Plug-In mithilfe des folgenden Befehls installiert:
           bin> plugin --install
           jdbc --url "http://xbib.org/repository/org/xbib/elasticsearch/plugin/elasticsearch-river-jdbc/1.3.4.4/elasticsearch-river-jdbc-1.3.4.4-plugin.zip"
  3. Schließlich wird die im ersten Schritt extrahierte Datei "SQLJDBC4.jar" ("C:\Program Files\Microsoft JDBC DRIVER 4.0 for SQL Server\sqljdbc_4.0\enu\SQLJDBC4.jar") in den Ordner "lib" im Elasticsearch-Verzeichnis ("C:\Program Files\Elasticsearch\lib") kopiert. Wenn das erfolgt ist, muss der Windows-Dienst neu gestartet werden.

Damit sind jetzt alle erforderlichen Plug-Ins installiert worden. Um zu überprüfen, ob die Installationen erfolgreich abgeschlossen wurden, muss der folgende Befehl als HTTP GET-Anforderung gesendet werden:

http://localhost:9200/_nodes/_all/plugins

In der Antwort sollten beide installierten Plug-Ins aufgelistet sein, wie in Abbildung 2 dargestellt.

Die installlierten Plug-Ins
Abbildung 2 Die installlierten Plug-Ins

Einrichten von SQL Server

Um JDBC River mit SQL Server zu verwenden, muss der Zugriff auf die SQL Server-Instanz über TCP/IP möglich sein, der standardmäßig deaktiviert ist. Das Aktivieren ist jedoch einfach – es ist nur erforderlich, den SQL Server-Konfigurations-Manager zu öffnen und unter der SQL Server-Netzwerkkonfiguration für die SQL Server-Instanz, zu der die Verbindung hergestellt werden soll, den Wert von "Status" für das TCP/IP-Protokoll auf "Aktiviert" festzulegen, wie in Abbildung 3 dargestellt. Wenn das geschehen ist, sollte es möglich sein, sich mithilfe von Management Studio mit "localhost 1433" als Servername (Port 1433 ist der Standardport für den Zugriff auf SQL Server per TCP/IP) bei der SQL Server-Instanz anzumelden.

Aktivieren von TCP/IP für die SQL Server-Instanz
Abbildung 3 Aktivieren von TCP/IP für die SQL Server-Instanz

Durchsuchbar Machen der Quellen

Alle erforderlichen Plug-Ins wurden installiert, es ist jetzt an der Zeit, die Daten zu laden. Wie bereits zuvor erwähnt, werden drei verschiedene Datenquellen verwendet (JSON-Dokumente, Dateien und eine Auftragstabelle aus einer SQL Server-Datenbank), die ich in Elasticsearch indizieren lassen möchte. Diese Datenquellen lassen sich auf viele verschiedene Weisen indizieren, ich möchte jedoch zeigen, wie einfach das mithilfe einer .NET-basierten Anwendung geht, die ich implementiert habe. Daher muss ich als Voraussetzung für die Indizierung eine externe Bibliothek mit dem Namen NEST (bit.ly/1vZjtCf) für mein Projekt installieren; dabei handelt es sich nur um einen verwalteten Wrapper um eine Elasticsearch-Webschnittstelle. Da diese Bibliothek auf NuGet zur Verfügung steht, beschränkt sich das Einbinden in das Projekt auf das Ausführen eines einzelnen Befehls in der Package Manager Console:

PM> Install-Package NEST

Da jetzt die NEST-Bibliothek in der Lösung verfügbar ist, kann ein neues Klassenbibliotheksprojekt mit dem Namen "ElasticSearchRepository" erstellt werden. Ich habe diesen Namen verwendet, da ich mich entschieden habe, alle Funktionsaufrufe aus der Klasse "ElasticClient" (die in der NEST-Bibliothek enthalten ist) vom Rest der Lösung getrennt zu halten. Auf diese Weise wird das Projekt dem Repository-Entwurfsmuster ähnlich, das verbreitet in auf Entity Framework basierenden Anwendungen verwendet wird, und sollte daher leicht zu verstehen sein. Außerdem kommen in diesem Projekt nur drei Klassen vor: die Klasse "BaseRepository", die die ererbten Klassen und die Instanz des ElasticClient initialisiert und verfügbar macht, und die bei den anderen Repositoryklassen:

  • "IndexRepository" – eine Klasse mit Lese-/Schreibzugriff, die zum Verändern von Indizes, Festlegen von Zuordnungen und Hochladen von Dokumenten verwendet wird.
  • "DiscoveryRepository" – eine schreibgeschützte Klasse, die für API-basierte Suchvorgänge verwendet wird.

Abbildung 4 zeigt die Struktur der Klasse "BaseRepository" mit einer geschützten Eigenschaft vom Typ "ElasticClient". Dieser Typ, der als Teil der NEST-Bibliothek bereitgestellt wird, zentralisiert die Kommunikation zwischen dem Elasticsearch-Server und der Clientanwendung. Zum Erstellen einer Instanz kann eine URL an den Elasticsearch-Server übergeben werden, und zwar in Form eines optionalen Klassenkonstruktorparameters. Wenn der Parameter Null ist, wird der Standardwert "http://localhost:9200" verwendet.

Abbildung 4 Die BaseRepository-Klasse

namespace ElasticSearchRepository
{
  using System;
  using Nest;
  public class BaseRepository
  {
    protected ElasticClient client;
    public BaseRepository(Uri elastiSearchServerUrl = null)
    {
      this.client = elastiSearchServerUrl != null ?
        new ElasticClient(new ConnectionSettings(elastiSearchServerUrl)) :
        : new ElasticClient();
    }
  }
}

Bei einsatzbereitem Client werden zuerst die Kundendaten indizieren; dies ist das einfachste Szenario, da keine zusätzlichen Plug-Ins erforderlich sind:

public class Client
{
  public int Id { get; set; }
  public string Name { get; set; }
  public string Surname { get; set; }
  public string Email { get; set; }
}

Zum Indizieren von Daten dieses Typs werden die Instanz des Elasticsearch-Clients sowie die Funktion "Index<T>" aufgerufen, wobei "T" den Typ der Kundenklasse ("Client" ist der Name der Tabelle) angibt, der durch serialisierte Daten dargestellt wird, die von der API zurückgegeben werden. Diese allgemeine Funktion nimmt drei Parameter an: die Instanz der Objektklasse "T", einen Zielindexnamen und einen Zuordnungsnamen innerhalb des Index:

public bool IndexData<T>(T data, string indexName =
  null, string mappingType = null)
  where T : class, new()
  {
    if (client == null)
    {
      throw new ArgumentNullException("data");
    }
    var result = this.client.Index<T>(data,
      c => c.Index(indexName).Type(mappingType));
    return result.IsValid;
  }

Die beiden letzten Parameter sind optional, da NEST seine Standardlogik für die Erstellung eines Zielindexnamens auf der Grundlage des allgemeinen Typs einsetzt.

Im Anschluss sollen die Marketingdokumente indiziert werden, die auf die Firmenprodukte bezogen sind. Da diese Dateien auf einer Netzwerkfreigabe gespeichert sind, können Informationen über die einzelnen Dokumente in einer einfachen Klasse "MarketingDocument" verpackt werden. Hier muss erwähnt werden, dass ein in Elasticsearch zu indizierendes Dokument als Base64-codierte Zeichenfolge hochgeladen werden muss:

public class MarketingDocument
{
  public int Id { get; set; }
  public string Title { get; set; }
  public string ProductName { get; set; }
  // Base64-encoded file content.
  public string Document { get; set; }
}

Die Klasse ist bereit, daher kann ich den ElasticClient verwenden, um ein bestimmtes Feld in der Klasse "MarketingDocument" als Anlage zu kennzeichnen. Dies lässt sich durch Erstellen eines neuen Index mit dem Namen "products" erreichen, dem eine neue Marketingzuordnung hinzugefügt wird (der Einfachheit halber wird als Klassenname der Name der Zuordnung verwendet):

private void CreateMarketingIndex()
  {
    client.CreateIndex("products", c =>
      c.AddMapping<Marketing>
      (m => m.Properties(ps =>ps.Attachment(a =>
            a.Name(o =>o.Document).TitleField(t =>
            t.Name(x => x.Name)
            TermVector(TermVectorOption.WithPositionsOffsets)
        )))));
  }

Da jetzt sowohl der .NET-Typ und die Zuordnung für die Marketingdaten als auch die Definition für eine Anlage vorhanden sind, kann mit der Indizierung der Dateien in der gleichen Weise wie bei den Kundendaten begonnen werden:

var documents = GetMarketingDocumentsMock();
documents.ForEach((document) =>
{
  indexRepository.IndexData<MarketingDocument>(document, "marketing");
});

Der letzte Schritt ist die Einrichtung von JDBC River in Elasticsearch. Leider unterstützt NEST JDBC River derzeit noch nicht. Theoretisch kann eine JDBC River-Zuordnung mithilfe einer Raw-Funktion erstellt werden, um eine Raw-Anforderung mit JSON zu senden, aber wir wollen es doch nicht komplizierter machen als nötig. Daher werden zum Abschließen des Erstellungsvorgangs der Zuordnung die folgenden Parameter angegeben:

  • Eine Verbindungszeichenfolge für die SQL Server-Datenbank
  • Eine SQL-Abfrage, die zum Abfragen von Daten verwendet wird
  • Einen Aktualisierungszeitplan
  • Einen Zielindexnamen und -typ (optional)

(Eine vollständige Liste der konfigurierbaren Parameter befindet sich unter bit.ly/12CK8Zu.)

Zum Erstellen einer neuen JDBC River-Zuordnung muss eine PUT-Anforderung mit einem darin angegebenen Anforderungshauptteil an die folgende URL gesendet werden: 

http://localhost:9200/_river/{river_name}/_meta

In dem Beispiel in Abbildung 5 habe ich per PUT einen Anforderungshauptteil zum Erstellen einer neuen JDBC River-Zuordnung gesendet, die eine Verbindung mit der auf der lokalen SQL Server-Instanz, auf die per TCP/IP über Port 1433 zugegriffen werden kann, gehosteten Contoso-Datenbank herstellt.

Abbildung 5 HTTP PUT-Anforderung zum Erstellen einer neuen JDBC River-Zuordnung

PUT http://localhost:9200/_river/orders_river/_meta
{
"type":"jdbc",
"jdbc":
  {
  "driver": "com.microsoft.sqlserver.jdbc.SQLServerDriver",
  "url":"jdbc:sqlserver://127.0.0.1:1433;databaseName=Contoso",
  "user":"elastic",
  "password":"asd",
  "sql":"SELECT *  FROM dbo.Orders",
  "index" : "clients",
  "type" : "orders",
  "schedule": "0/30 0-59 0-23 ? * *"
  }
}

Sie verwendet den Anmeldenamen "elastic" und das Kennwort "asd", um den Benutzer zu authentifizieren und den folgenden SQL-Befehl auszuführen:

SELECT * FROM dbo.Orders

Jede von dieser SQL-Abfrage zurückgegebene Datenzeile wird unter dem Index "Clients" im Zuordnungstyp "Orders" erstellt, und die Indizierung erfolgt alle 30 Sekunden (in der Cron-Schreibweise dargestellt; weitere Informationen finden Sie unter bit.ly/1hCcmnN).

Wenn dieser Vorgang abgeschlossen ist, sollten sich in der Elasticsearch-Protokolldatei ("/logs/ elasticsearch.log") Informationen in dieser Art finden:

[2014-10-2418:39:52,190][INFO][river.jdbc.RiverMetrics]
pipeline org.xbib.elasticsearch.plugin.jdbc.RiverPipeline@70f0a80d
complete: river jdbc/orders_river metrics: 34553 rows, 6.229481683638776 mean, 
  (0.0 0.0 0.0), ingest metrics: elapsed 2 seconds, 
  364432.0 bytes bytes, 1438.0 bytes avg, 0.1 MB/s

Wenn etwas mit der River-Konfiguration nicht in Ordnung ist, findet sich die entsprechende Fehlermeldung ebenfalls im Protokoll.

Durchsuchen der Daten

Sobald alle Daten im Elasticsearch-Modul indiziert wurden, kann mit dem Abfragen begonnen werden. Natürlich können einfache Anforderungen an den Elasticsearch-Server gesendet werden, um mehrere Indizes und Zuordnungstypen zur gleichen Zeit abzufragen, aber wir möchten etwas Nützlicheres und Realitätsnäheres aufbauen. Daher teile ich das Projekt in drei verschiedene Komponenten auf. Die erste Komponente, die ich hier bereits vorgestellt habe, ist Elasticsearch, sie steht über "http://localhost:9200/" zur Verfügung. Die zweite Komponente ist eine API, die mithilfe von Web API 2-Technologie erstellt werden soll. Die letzte Komponente ist eine Konsolenanwendung, die ich verwende, um meine Indizes in Elasticsearch einzurichten und es mit Daten zu füllen.

Zum Erstellen des neuen Web API 2-Projekts muss zuerst ein leeres ASP.NET-Webanwendungsprojekt erstellt und anschließend in der Package Manager Console der folgenden Installationsbefehl ausgeführt werden:

Install-Package Microsoft.AspNet.WebApi

Nachdem das Projekt erstellt wurde, besteht der nächste Schritt im Hinzufügen eines neuen Controllers, der dazu verwendet wird, Abfrageanforderungen vom Client zu verarbeiten und sie an Elasticsearch zu übergeben. Das Hinzufügen eines neuen Controllers mit dem Namen DiscoveryController besteht ausschließlich im Hinzufügen eines neuen Elements einer Web-API-ApiController-Klasse (v2.1). Und es muss eine Suchfunktion implementiert werden, die über die URL verfügbar gemacht wird: http://website/api/discovery/search?searchTerm=user_input:

[RoutePrefix("api/discovery")]
public class DiscoveryController : ApiController
{
  [HttpGet]
  [ActionName("search")]
  public IHttpActionResult Search(string searchTerm)
  {
    var discoveryRepository = new DiscoveryRepository();
    var result = discoveryRepository.Search(searchTerm);
    return this.Ok(result);
  }
}

Wenn das Web API 2-Modul eine Antwort aufgrund einer selbstreferenziellen Schleife nicht serialisieren kann, müssen Sie in der Datei "WebApiConfig.cs", die sich im Ordner "AppStart" befindet, Folgendes hinzufügen:

GlobalConfiguration.Configuration
.Formatters
.JsonFormatter
.SerializerSettings
.ReferenceLoopHandling =
      ReferenceLoopHandling.Ignore;

Wie in Abbildung 6 dargestellt, wurde im Hauptteil des erstellten Controllers eine Klasse vom Typ "DiscoveryRepository" instanziiert; diese stellt lediglich einen Wrapper für den Typ "ElasticClient" aus der NEST-Bibliothek dar. Innerhalb dieses nicht allgemeinen, schreibgeschützten Repositorys wurden zwei Typen Suchfunktionen implementiert, die beide einen dynamischen Typ zurückgeben. Dieser Teil ist wichtig, denn wenn dies in beiden Funktionshauptteilen ausgeführt wird, bleiben die Abfragen nicht auf nur einen Index beschränkt; stattdessen werden alle Indizes und alle Typen zur gleichen Zeit abgefragt. Das bedeutet, dass die Ergebnisse eine unterschiedliche Struktur aufweisen werden (verschiedenen Typen angehören). Der einzige Unterschied zwischen den Funktionen ist die Abfragemethode. In der ersten Funktion wird nur eine QueryString-Methode (bit.ly/1mQEEg7) verwendet, die eine Suche auf exakte Übereinstimmung darstellt, und in der zweiten Funktion wird eine Fuzzy-Methode (bit.ly/1uCk7Ba) verwendet, die eine unscharfe Suche über die Indizes ausführt.

Abbildung 6 Implementierung der beiden Suchtypen

namespace ElasticSearchRepository
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  public class DiscoveryRepository : BaseRepository
  {
    public DiscoveryRepository(Uri elastiSearchServerUrl = null)
      : base(elastiSearchServerUrl)
    {
    }
    ///<summary>  
    public List<Tuple<string, string>> SearchAll(string queryTerm)
    {
      var queryResult=this.client.Search<dynamic>(d =>
        d.AllIndices()
        .AllTypes()
        .QueryString(queryTerm));
      return queryResult
        .Hits
        .Select(c => new Tuple<string, string>(
          c.Indexc.Source.Name.Value))
        .Distinct()
        .ToList();
     }
     ///<summary>  
     public dynamic FuzzySearch(string queryTerm)
     {
       return this.client.Search<dynamic>(d =>
         d.AllIndices()
         .AllTypes()
         .Query(q => q.Fuzzy(f =>
           f.Value(queryTerm))));
     }
  }
}

Die fertig gestellte API kann jetzt ausgeführt und es kann mit dem Testen begonnen werden, indem einfach GET-Anforderungen an "http://website:port/api/discovery/search?searchTerm=user_input" gesendet und Benutzereingaben als Wert des Abfrageparameters "searchTerm" übergeben werden. Daher zeigt Abbildung 7 die Ergebnisse, die von der API für den Suchbegriff "scrum" generiert werden. Wie bereits im Screenshot hervorgehoben, hat eine Suchfunktion eine Abfrage über alle Indizes in den Datenspeichern ausgeführt und Treffer aus mehreren Indizes zugleich zurückgegeben.

API-Suchergebnisse für den Begriff "scrum"
Abbildung 7 API-Suchergebnisse für den Begriff "scrum"

Durch Implementieren der erstellten API-Schicht wurde die Möglichkeit geschaffen, mehrere Clients (wie etwa eine Website oder eine mobile App) zu Implementieren, die die Ergebnisse nutzen können. Damit steht die Möglichkeit bereit, die Funktionalität einer Unternehmenssuche für Endbenutzer zugänglich zu machen. Eine Beispielimplementierung des AutoVervollständigen-Steuerelements für einen auf ASP.NET MVC 4 basierenden Webclient finden Sie in meinem Blog unter bit.ly/1yThHiZ.

Zusammenfassung

Big Data hat die technologisch dominierten Felder erheblich bereichert, sowohl um Chancen als auch um Herausforderungen. Eine der Herausforderungen, die potenziell auch eine große Chance darstellt, ist die Möglichkeit, eine schnelle Suche für Daten in der Petabytegrößenordnung zu Implementieren, ohne dazu den genauen Speicherort der Daten im Datenuniversum kennen zu müssen. In diesem Artikel habe ich beschrieben, wie Unternehmenssuche implementiert werden kann, und das für .NET Framework in Verbindung mit den Bibliotheken Elasticsearch und NEST dargestellt.


Damian Zapart ist ein Entwicklungsleiter bei Citigroup Inc. mit dem Schwerpunkt Unternehmenslösungen. Zugleich ist Zapart als ausgewiesener Programmierungsexperte an innovativen Technologien, Entwurfsmustern und Big Data interessiert. Weitere Informationen über ihn erhalten Sie in seinem Blog unter bit.ly/1suoKN0.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Evren Onem (D&B) und Bruno Terkaly (Microsoft)
Evren Onem (D&B) ist ein leitender Software-Engineer bei D&B. Sein Aufgabenbereich besteht im Entwickeln und Implementieren hochgradig skalierbarer REST-APIs. Zu später Stunde erforscht er außerdem kognitive Ad-hoc-Funknetze.

Bruno Terkaly ist ein leitender Software-Engineer bei Microsoft mit dem Ziel, die Entwicklung von branchenweit führenden Anwendungen und Diensten über Gerätegrenzen hinweg zu ermöglichen. Er ist unter Technologiegesichtspunkten für die wichtigsten Cloud-basierten und mobilen Geschäftsmodelle in den gesamten USA und darüber hinaus zuständig. Er hilft Partnern, die Marktreife ihrer Anwendungen zu erreichen, indem er bei der Beurteilung, Entwicklung und Bereitstellung von ISVs architektonische Führung und sein tief greifendes technisches Engagement aufbietet. Er arbeitet außerdem eng mit den Entwicklergruppen für die Bereich Cloud und Mobil zusammen, gibt Feedback und nimmt auf die Planung Einfluss.