Backend

Azure Search. Jak stworzyć zaawansowaną wyszukiwarkę w kilku krokach (cz.2)

Azure

W poprzedniej części artykułu opisałem przykładowy scenariusz: chcieliśmy stworzyć przeglądarkę, która będzie umożliwiała podpowiadanie, wyszukiwanie po kilku polach, filtrowanie, wypisywanie wyników nawet, gdy użytkownik popełni dany błąd. Mogłaby też przeszukiwać pliki tekstowe i zdjęcia. Pisząc to samodzielnie zajęłoby to nam bardzo dużo czasu, dlatego skorzystaliśmy z Azure i usługi Search. W drugiej części artykułu omówimy scenariusz, w którym tworzymy wyszukiwarkę dla strony internetowej z wiadomościami, w której bazie są tytuły artykułów, oraz treści tych artykułów trzymane są na Blobie.


Kacper Świsłocki. .NET Developer w białostockiej firmie Elastic Cloud Solutions. Młody programista, początkujący prelegent. Występował m.in. podczas wydarzenia Śląska Grupa Microsoft Meetup, gdzie opowiadał o tym, jak stworzyć zaawansowaną wyszukiwarkę za pomocą Azure Search.


Tworzymy projekt w .Necie – aplikację konsolową, której zadaniem będzie konfiguracja naszej usługi. Aby móc odwoływać się do naszej usługi potrzebujemy jej nazwy, oraz klucza administratora. Musimy również zainstalować SDK – Microsoft.Azure.Search do naszego projektu. (Poniżej znajdują się tylko kody najważniejszych funkcji. Pełny kod będzie znajdować się na repozytorium: https://github.com/swislockikacper/AzureSearchConfiguration).

Tworzymy dwa źródła danych. Jedno do informacji znajdujących się w bazie, drugie do plików znajdujących się na blobie.

private void CreateContentDataSource()
{
        	Console.WriteLine("Creating data source...");
 
        	var dataSource = DataSource.AzureSql(
            		name: contentDataSourceName,
            		sqlConnectionString: dbConnectionString,
            		tableOrViewName: contentTable);
 
        	try
        	{
                	searchClient.DataSources.CreateOrUpdate(dataSource);
            		Console.WriteLine("Done");
            		serviceWorks = true;
        	}
        	catch
        	{
            		Console.WriteLine("Something went wrong");
            		serviceWorks = false;
        	}
}
 
private void CreateBlobDataSource()
{
        	Console.WriteLine("Creating data source (blob)...");
 
        	var dataSource = DataSource.AzureBlobStorage(
            		name: filesDataSourceName,
            		storageConnectionString: blobConnectionString,
            		containerName: $"articles");
 
        	try
        	{
                	searchClient.DataSources.CreateOrUpdate(dataSource);
            		Console.WriteLine("Done");
            		serviceWorks = true;
       	}
        	catch (Exception e)
        	{
            		Console.WriteLine("Something went wrong");
            		serviceWorks = false;
            }
}

Tworzymy jeden indeks, który będzie zawierał dane zarówno z bazy danych, jak i z Bloba. Definiujemy w nawiasach, jakiego typu są to pola oraz co możemy z nimi zrobić, tak jak było to przy tworzeniu indeksu na portalu.

class ArticleIndex
{
    	[Key]
    	[IsFilterable, IsSearchable, IsRetrievable(true)]
    	public string Title { get; set; }
 
    	[IsRetrievable(true)]
    	public string Id { get; set; }
 
    	[IsFilterable, IsSearchable, IsRetrievable(true)]
    	public string Content { get; set; }
}

Będą to wszystkie dane z tabeli bazy danych oraz pole content (zawartość pliku tekstowego) z Bloba. Ponadto należy połączyć te dane jakimś polem, które będzie zawierała tabela oraz pliki z Bloba. Wykorzystamy do tego tytuł artykułu, który jest zapisywany w naszej bazie.

Najważniejszą częścią jest dodanie meta danych do bloba, również z tytułem artykułu, aby poinformować usługę, do którego indeksu ma trafić zawartość pliku. Następnie należy zrzutować dodawaną meta daną na odpowiednie pole w indeksie, co zaimplementowano poniżej.

Dane z bazy oraz przykład meta danych znajdujące się na Blobie.

Ponadto do indeksu możemy dodać suggester, oraz scoring profile.

  private List<Suggester> CreateSuggester() =>
        	new List<Suggester> { new Suggester(suggesterName, new[] { "Title" }) };
 
 

    private List<ScoringProfile> CreateScoringProfile()
    {
        	var weights = new Dictionary<string, double>
        	{
            		{ "Title", 6 }, { "Content", 3 }
        	};
 
        	return new List<ScoringProfile>
        	{
            		new ScoringProfile(scoringProfileName, new TextWeights(weights))
        	};
    }

Tworzenie indeksu

private void CreateIndex()
{
        	Console.WriteLine("Creating index...");
        	var indexExists = searchClient.Indexes.Exists(indexName);
 
        	if (indexExists)
                	searchClient.Indexes.Delete(indexName);
 
        	var index = new Index(
            		name: indexName,
            		fields: FieldBuilder.BuildForType<ArticleIndex>(),
            		scoringProfiles: CreateScoringProfile(),
            		suggesters: CreateSuggester());
 
        	try
        	{
                	searchClient.Indexes.Create(index);
            		Console.WriteLine("Done");
            		serviceWorks = true;
        	}
        	catch (Exception e)
        	{
            		Console.WriteLine("Something went wrong");
            		serviceWorks = false;
            }
}

Musimy teraz stworzyć dwa indeksery, które będą ściągać dane z naszych źródeł. Musimy jednak pamiętać o zmapowaniu pól title z meta danych boba, oraz zawartości pliku. Wykona to poniższa metoda.

   private List<FieldMapping> AddFieldsToMappingInBlob()
        	=> new List<FieldMapping> { new FieldMapping("title", "Title"), new FieldMapping("content", "Content") };
  


private void CreateBlobIndexer()
{
        Console.WriteLine("Creating indexer (blob)...");
 
        var indexerExists = searchClient.Indexers.Exists(filesIndexerName);
 
        if (indexerExists)
               searchClient.Indexers.Delete(filesIndexerName);
 
        var indexer = new Indexer(
            	name: filesIndexerName,
            	dataSourceName: filesDataSourceName,
            	targetIndexName: indexName,
            	schedule: new IndexingSchedule(TimeSpan.FromDays(1)),
            	fieldMappings: AddFieldsToMappingInBlob()
            	);
 
       try
        {
                searchClient.Indexers.Create(indexer);
                searchClient.Indexers.Run(filesIndexerName);
            	  Console.WriteLine("Done");
            	   serviceWorks = true;
        }
        catch (Exception e)
        {
            	Console.WriteLine("Something went wrong");
            	serviceWorks = false;
        }
}
  
private void CreateContentIndexer()
{
        	Console.WriteLine("Creating indexer...");
 
     	var indexerExists = searchClient.Indexers.Exists(contentIndexerName);
 
        	if (indexerExists)
                searchClient.Indexers.Delete(contentIndexerName);
 
        	var indexer = new Indexer(
            		name: contentIndexerName,
            		dataSourceName: contentDataSourceName,
            		targetIndexName: indexName,
            		schedule: new IndexingSchedule(TimeSpan.FromDays(1)));
 
        	try
        	{
                	searchClient.Indexers.Create(indexer);
                	searchClient.Indexers.Run(contentIndexerName);
            		System.Console.WriteLine("Done");
            		serviceWorks = true;
        	}
        	catch (Exception e)
        	{
            		Console.WriteLine("Something went wrong");
            		serviceWorks = false;
            }
}

Odpalamy naszą aplikację. Jeżeli wszystko poszło bez błędów, możemy pisać zapytania.
W tym przypadku również napiszemy konsolówkę. Kod całego programu znajduje się w repozytorium pod adresem: https://github.com/swislockikacper/AzureSearchQueryExample.

Funkcja, która implementuje zapytanie, wygląda następująco:

private DocumentSearchResult Search(string query)
{
        	var searchParameters = new SearchParameters()
        	{
            		IncludeTotalResultCount = true,
            		ScoringProfile = "scoring-profile",
            		QueryType = QueryType.Full
        	};
 
        	return indexClient.Documents.Search(query, searchParameters);
}

Po otrzymaniu wyników, należy je zdeserializować.

private IEnumerable<Article> DeserializeResults(DocumentSearchResult response)
{
        	var results = new List<Article>();
 
        	foreach (var result in response.Results)
        	{
            		results.Add(new Article
            		{
                		Id = result.Document["Id"].ToString(),
                		Title = result.Document["Title"].ToString(),
                		Content = result.Document["Content"].ToString()
  	          	});
            }
            return results;
}

Podsumowanie

Azure Search jest bardzo rozbudowanym i pożytecznym narzędziem — co ważne jest cały czas rozwijany. Nie jest także skomplikowany, wszystko można stworzyć z poziomu kodu, lub też wyklinać. W wielu przypadkach może ułatwić życie. Nie przedstawiałem tutaj samego działania podpowiadania oraz innych ciekawych funkcjonalności, jednak korzystanie z nich jest równie proste, co samo wyszukiwanie.

najwięcej ofert html

Podobne artykuły

[wpdevart_facebook_comment curent_url="https://justjoin.it/blog/azure-search-stworzyc-zaawansowana-wyszukiwarke-kilku-krokach-cz-2" order_type="social" width="100%" count_of_comments="8" ]