如何在 C# .NET 应用程序中使用 Microsoft.Azure.Search

本文介绍如何使用 C# 和旧版客户端库创建和管理搜索对象,azure SDK for .NET 中的 Microsoft.Azure.Search(版本 10)。

版本 10 是 Microsoft.Azure.Search 包的最后一个版本。 接下来,Azure SDK 团队将在 Azure.Search.Documents 推出新功能。

注释

如果你有现有或飞行中的开发项目,则可以继续使用版本 10。 对于新项目或使用新功能,应过渡到 新库。

关于第 10 版

SDK 包含几个客户端库,可用于管理索引、数据源、索引器和同义词映射,以及上传和管理文档和执行查询,所有这些库无需处理 HTTP 和 JSON 的详细信息。 这些客户端库都作为 NuGet 包分发。

主 NuGet 包是 Microsoft.Azure.Search,它是一个元包,其中包含所有其他包作为依赖项。 如果刚开始,或者知道应用程序将需要 Azure 认知搜索的所有功能,请使用此包。

SDK 中的其他 NuGet 包包括:

  • Microsoft.Azure.Search.Data:如果使用 Azure 认知搜索开发 .NET 应用程序,并且只需查询或更新索引中的文档,请使用此包。 如果还需要创建或更新索引、同义词映射或其他服务级别资源,请改用 Microsoft.Azure.Search 包。
  • Microsoft.Azure.Search.Service:如果要在 .NET 中开发自动化来管理 Azure 认知搜索索引、同义词映射、索引器、数据源或其他服务级别资源,请使用此包。 如果只需要查询或更新索引中的文档,请改用 Microsoft.Azure.Search.Data 包。 如果需要 Azure 认知搜索的所有功能,请改用 Microsoft.Azure.Search 包。
  • Microsoft.Azure.Search.Common:Azure 认知搜索 .NET 库所需的常见类型。 无需直接在应用程序中使用此包。 它仅用作一个依赖项。

各种客户端库定义类,例如 Index、Field和 Document以及在 Indexes.Create 和 Documents.Search 类上的操作,如 SearchServiceClient 和 SearchIndexClient。 这些类已组织成以下命名空间:

如果想要提供有关 SDK 的未来更新的反馈,请参阅我们的 反馈页 或在 GitHub 上创建问题,并在问题标题中提及“Azure 认知搜索”。

.NET SDK 针对 Azure 认知搜索 REST API 2019-05-06。 此版本包括支持 复杂类型、AI 扩充、自动完成,以及索引 Azure Blob 时 JsonLines 分析模式。

此 SDK 不支持 管理操作,例如创建和扩展搜索服务以及管理 API 密钥。 如果需要从 .NET 应用程序管理搜索资源,可以使用 Azure 认知搜索 .NET 管理 SDK。

升级到 v10

如果您已在使用旧版 Azure 认知搜索 .NET SDK,并且想要升级到最新的普遍可用版本,本文 说明如何升级。

SDK 要求

  1. Visual Studio 2017 或更高版本。
  2. 你自己的 Azure 认知搜索服务。 若要使用 SDK,需要服务的名称和一个或多个 API 密钥。 在门户中创建服务 将帮助你完成这些步骤。
  3. 使用 Visual Studio 中的“管理 NuGet 包”功能来下载 Azure 认知搜索 .NET SDK 的 NuGet 包。 只需在 NuGet.org 上搜索包名称 Microsoft.Azure.Search(或者上述包名称之一(如果只需要功能子集)。

Azure 认知搜索 .NET SDK 支持面向 .NET Framework 4.5.2 及更高版本以及 .NET Core 2.0 及更高版本的应用程序。

核心场景

在您的搜索应用程序中,需要执行多项任务。 在本教程中,我们将介绍以下核心方案:

  • 创建索引
  • 使用文档填充索引
  • 使用全文搜索和筛选器搜索文档

以下示例代码演示了其中每个方案。 请随意在自己的应用程序中使用代码片段。

概述

我们将探索的示例应用程序会创建一个名为“hotels”的新索引,使用几个文档填充它,然后执行一些搜索查询。 下面是主要程序,其中显示了整体流程:

// This sample shows how to delete, create, upload documents and query an index
static void Main(string[] args)
{
    IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
    IConfigurationRoot configuration = builder.Build();

    SearchServiceClient serviceClient = CreateSearchServiceClient(configuration);

    string indexName = configuration["SearchIndexName"];

    Console.WriteLine("{0}", "Deleting index...\n");
    DeleteIndexIfExists(indexName, serviceClient);

    Console.WriteLine("{0}", "Creating index...\n");
    CreateIndex(indexName, serviceClient);

    ISearchIndexClient indexClient = serviceClient.Indexes.GetClient(indexName);

    Console.WriteLine("{0}", "Uploading documents...\n");
    UploadDocuments(indexClient);

    ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(configuration);

    RunQueries(indexClientForQueries);

    Console.WriteLine("{0}", "Complete.  Press any key to end application...\n");
    Console.ReadKey();
}

注释

可以在 gitHub上找到本演练中使用的示例应用程序的完整源代码。

我们将逐步完成此步骤。 首先,我们需要创建新的 SearchServiceClient。 此对象允许你管理索引。 若要构造一个,需要提供 Azure 认知搜索服务名称和管理 API 密钥。 可以在 appsettings.json的 文件中输入此信息。

private static SearchServiceClient CreateSearchServiceClient(IConfigurationRoot configuration)
{
    string searchServiceName = configuration["SearchServiceName"];
    string adminApiKey = configuration["SearchServiceAdminApiKey"];

    SearchServiceClient serviceClient = new SearchServiceClient(searchServiceName, new SearchCredentials(adminApiKey));
    return serviceClient;
}

注释

如果您提供了不正确的密钥(例如,在需要管理员密钥的地方使用查询密钥),那么当您第一次调用它的操作方法时,SearchServiceClient 会抛出一个“禁止”的 CloudException 错误消息,例如 Indexes.Create。 如果发生这种情况,请仔细检查 API 密钥。

接下来的几行调用方法以创建名为“hotels”的索引,如果它已存在,请先将其删除。 稍后我们将逐步讲解这些方法。

Console.WriteLine("{0}", "Deleting index...\n");
DeleteIndexIfExists(indexName, serviceClient);

Console.WriteLine("{0}", "Creating index...\n");
CreateIndex(indexName, serviceClient);

接下来,需要填充索引。 若要填充索引,我们需要一个 SearchIndexClient。 有两种方法可以获取:通过构造它,或在 SearchServiceClient 上调用 Indexes.GetClient。 为了方便起见,我们使用后者。

ISearchIndexClient indexClient = serviceClient.Indexes.GetClient(indexName);

注释

在典型的搜索应用程序中,索引管理和填充可由独立于搜索查询的组件处理。 Indexes.GetClient 方便用于填充索引,因为它可以省去提供其他 SearchCredentials的麻烦。 它通过将您用于创建 SearchServiceClient 的管理密钥传递给新的 SearchIndexClient来实现这一点。 但是,在执行查询的应用程序的一部分,最好直接创建 SearchIndexClient,以便传入查询密钥,这只允许读取数据,而不是管理密钥。 这符合最低特权原则,有助于使应用程序更安全。 可在此处 了解有关管理密钥和查询密钥的详细信息。

现在我们有了一个 SearchIndexClient,就可以填充索引了。 索引填充由我们稍后介绍的另一种方法完成。

Console.WriteLine("{0}", "Uploading documents...\n");
UploadDocuments(indexClient);

最后,我们执行一些搜索查询并显示结果。 这次我们使用不同的 SearchIndexClient:

ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(indexName, configuration);

RunQueries(indexClientForQueries);

稍后我们将仔细了解 RunQueries 方法。 下面是创建新 SearchIndexClient的代码:

private static SearchIndexClient CreateSearchIndexClient(string indexName, IConfigurationRoot configuration)
{
    string searchServiceName = configuration["SearchServiceName"];
    string queryApiKey = configuration["SearchServiceQueryApiKey"];

    SearchIndexClient indexClient = new SearchIndexClient(searchServiceName, indexName, new SearchCredentials(queryApiKey));
    return indexClient;
}

这一次我们使用查询键,因为我们不需要对索引进行写入访问。 可以在 appsettings.json的 文件中输入此信息。

如果使用有效的服务名称和 API 密钥运行此应用程序,输出应如以下示例所示:(某些控制台输出已替换为“...”出于插图目的。)


Deleting index...

Creating index...

Uploading documents...

Waiting for documents to be indexed...

Search the entire index for the term 'motel' and return only the HotelName field:

Name: Secret Point Motel

Name: Twin Dome Motel


Apply a filter to the index to find hotels with a room cheaper than $100 per night, and return the hotelId and description:

HotelId: 1
Description: The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.

HotelId: 2
Description: The hotel is situated in a  nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.


Search the entire index, order by a specific field (lastRenovationDate) in descending order, take the top two results, and show only hotelName and lastRenovationDate:

Name: Triple Landscape Hotel
Last renovated on: 9/20/2015 12:00:00 AM +00:00

Name: Twin Dome Motel
Last renovated on: 2/18/1979 12:00:00 AM +00:00


Search the hotel names for the term 'hotel':

HotelId: 3
Name: Triple Landscape Hotel
...

Complete.  Press any key to end application... 

本文末尾提供了应用程序的完整源代码。

接下来,我们将仔细了解 Main调用的每个方法。

创建索引

创建 SearchServiceClient后,Main 删除“hotels”索引(如果已存在)。 该删除作由以下方法完成:

private static void DeleteIndexIfExists(string indexName, SearchServiceClient serviceClient)
{
    if (serviceClient.Indexes.Exists(indexName))
    {
        serviceClient.Indexes.Delete(indexName);
    }
}

此方法使用给定 SearchServiceClient 来检查索引是否存在,如果是,请将其删除。

注释

本文中的示例代码使用 Azure 认知搜索 .NET SDK 的同步方法,为简单起见。 建议在自己的应用程序中使用异步方法,使其保持可缩放且响应迅速。 例如,在上述方法中,可以使用 ExistsAsync 和 DeleteAsync,而不是 Exists 和 Delete。

接下来,Main 通过调用此方法创建新的“hotels”索引:

private static void CreateIndex(string indexName, SearchServiceClient serviceClient)
{
    var definition = new Index()
    {
        Name = indexName,
        Fields = FieldBuilder.BuildForType<Hotel>()
    };
    
    serviceClient.Indexes.Create(definition);
}

此方法创建一个新的 Index 对象,其中包含定义新索引架构的 Field 对象列表。 每个字段都有名称、数据类型和数个属性(定义其搜索行为)。 FieldBuilder 类通过检查给定 Field 模型类的公共属性和特性,使用反射来为索引创建 Hotel 对象的列表。 我们会在以后深入研究 Hotel 类。

注释

如果需要,始终可以直接创建 Field 对象的列表,而不是使用 FieldBuilder。 例如,你可能不想使用模型类,或者可能需要使用不想通过添加属性修改的现有模型类。

除了字段,还可以向索引添加计分配置文件、建议器或 CORS 选项(为简洁起见,从示例中省略这些参数)。 可以在 SDK 参考中找到有关 Index 对象及其构成部分的详细信息,以及 Azure 认知搜索 REST API 参考。

填充索引

Main 中的下一步将填充新创建的索引。 此索引填充在以下方法中完成:(某些代码替换为“...”出于说明目的。请参阅完整数据填充代码的完整示例解决方案。

private static void UploadDocuments(ISearchIndexClient indexClient)
{
    var hotels = new Hotel[]
    {
        new Hotel()
        {
            HotelId = "1",
            HotelName = "Secret Point Motel",
            ...
            Address = new Address()
            {
                StreetAddress = "677 5th Ave",
                ...
            },
            Rooms = new Room[]
            {
                new Room()
                {
                    Description = "Budget Room, 1 Queen Bed (Cityside)",
                    ...
                },
                new Room()
                {
                    Description = "Budget Room, 1 King Bed (Mountain View)",
                    ...
                },
                new Room()
                {
                    Description = "Deluxe Room, 2 Double Beds (City View)",
                    ...
                }
            }
        },
        new Hotel()
        {
            HotelId = "2",
            HotelName = "Twin Dome Motel",
            ...
            {
                StreetAddress = "140 University Town Center Dr",
                ...
            },
            Rooms = new Room[]
            {
                new Room()
                {
                    Description = "Suite, 2 Double Beds (Mountain View)",
                    ...
                },
                new Room()
                {
                    Description = "Standard Room, 1 Queen Bed (City View)",
                    ...
                },
                new Room()
                {
                    Description = "Budget Room, 1 King Bed (Waterfront View)",
                    ...
                }
            }
        },
        new Hotel()
        {
            HotelId = "3",
            HotelName = "Triple Landscape Hotel",
            ...
            Address = new Address()
            {
                StreetAddress = "3393 Peachtree Rd",
                ...
            },
            Rooms = new Room[]
            {
                new Room()
                {
                    Description = "Standard Room, 2 Queen Beds (Amenities)",
                    ...
                },
                new Room ()
                {
                    Description = "Standard Room, 2 Double Beds (Waterfront View)",
                    ...
                },
                new Room()
                {
                    Description = "Deluxe Room, 2 Double Beds (Cityside)",
                    ...
                }
            }
        }
    };

    var batch = IndexBatch.Upload(hotels);

    try
    {
        indexClient.Documents.Index(batch);
    }
    catch (IndexBatchException e)
    {
        // Sometimes when your Search service is under load, indexing will fail for some of the documents in
        // the batch. Depending on your application, you can take compensating actions like delaying and
        // retrying. For this simple demo, we just log the failed document keys and continue.
        Console.WriteLine(
            "Failed to index some of the documents: {0}",
            String.Join(", ", e.IndexingResults.Where(r => !r.Succeeded).Select(r => r.Key)));
    }

    Console.WriteLine("Waiting for documents to be indexed...\n");
    Thread.Sleep(2000);
}

此方法有四个部分。 第一个创建一个由 3 个 Hotel 个对象构成的数组,每个对象包含 3 个 Room 对象,这些对象将用作要上传到索引的输入数据。 为简单起见,此数据是硬编码的。 在自己的应用程序中,数据可能来自外部数据源,例如 SQL 数据库。

第二部分创建包含文档的 IndexBatch。 创建 Batch 时,指定要应用到 Batch 的操作,在这种情况下应调用 IndexBatch.Upload。 然后,Documents.Index 方法将批处理上传到 Azure 认知搜索索引。

注释

在此示例中,我们只是上传文档。 如果想要将更改合并到现有文档或删除文档,可以改为调用 IndexBatch.Merge、IndexBatch.MergeOrUpload 或 IndexBatch.Delete 创建 Batch。 还可以通过调用 IndexBatch.New,接受 IndexAction 对象的集合,在单个批处理中混合不同的操作。每个对象都告知 Azure 认知搜索对文档执行特定操作。 可以通过调用类似IndexAction、IndexAction.Merge的方法,为每个IndexAction.Upload创建其独立的操作。

此方法的第三部分是一个 catch 块,用于处理索引时的重要错误情况。 如果 Azure 认知搜索服务未能为批处理中某些文档编制索引,IndexBatchException将抛出 Documents.Index。 如果在服务负载过大的情况下为文档编制索引,则会发生此异常。 强烈建议您在代码中显式地处理这种情况。 可以延迟为失败的文档编制索引,并重试,也可以像此示例一样记录并继续执行,还可以执行其他操作,具体取决于应用程序对数据一致性的要求。

注释

可以使用 FindFailedActionsToRetry 方法构造一个新批处理,该批处理仅包含上一次调用 Index失败的动作。 讨论如何在 StackOverflow 上正确使用它。

最后,UploadDocuments 方法延迟了两秒钟。 索引在 Azure 认知搜索服务中异步进行,因此示例应用程序需要等待很短的时间,以确保文档可供搜索。 此类延迟通常仅在演示、测试和示例应用程序中是必需的。

.NET SDK 如何处理文档

你可能想知道 Azure 认知搜索 .NET SDK 如何能够将用户定义类的实例(如 Hotel)上传到索引。 为了帮助回答这个问题,让我们来看看 Hotel 类这个分类:

using System;
using Microsoft.Azure.Search;
using Microsoft.Azure.Search.Models;
using Microsoft.Spatial;
using Newtonsoft.Json;

public partial class Hotel
{
    [System.ComponentModel.DataAnnotations.Key]
    [IsFilterable]
    public string HotelId { get; set; }

    [IsSearchable, IsSortable]
    public string HotelName { get; set; }

    [IsSearchable]
    [Analyzer(AnalyzerName.AsString.EnLucene)]
    public string Description { get; set; }

    [IsSearchable]
    [Analyzer(AnalyzerName.AsString.FrLucene)]
    [JsonProperty("Description_fr")]
    public string DescriptionFr { get; set; }

    [IsSearchable, IsFilterable, IsSortable, IsFacetable]
    public string Category { get; set; }

    [IsSearchable, IsFilterable, IsFacetable]
    public string[] Tags { get; set; }

    [IsFilterable, IsSortable, IsFacetable]
    public bool? ParkingIncluded { get; set; }

    // SmokingAllowed reflects whether any room in the hotel allows smoking.
    // The JsonIgnore attribute indicates that a field should not be created 
    // in the index for this property and it will only be used by code in the client.
    [JsonIgnore]
    public bool? SmokingAllowed => (Rooms != null) ? Array.Exists(Rooms, element => element.SmokingAllowed == true) : (bool?)null;

    [IsFilterable, IsSortable, IsFacetable]
    public DateTimeOffset? LastRenovationDate { get; set; }

    [IsFilterable, IsSortable, IsFacetable]
    public double? Rating { get; set; }

    public Address Address { get; set; }

    [IsFilterable, IsSortable]
    public GeographyPoint Location { get; set; }

    public Room[] Rooms { get; set; }
}

首先需要注意的是,Hotel 类中每个公共属性的名称将映射到索引定义中具有相同名称的字段。 如果希望每个字段以小写字母开头(即“驼峰命名法”),则可以指示 SDK 使用类上的 [SerializePropertyNamesAsCamelCase] 属性自动将属性名称映射为驼峰命名法。 此方案在执行数据绑定的 .NET 应用程序中很常见,其中目标架构不受应用程序开发人员的控制,而无需违反 .NET 中的“Pascal case”命名准则。

注释

Azure 认知搜索 .NET SDK 使用 NewtonSoft JSON.NET 库将自定义模型对象序列化和反序列化为 JSON 数据格式。 如果需要,可以自定义此序列化。 有关详细信息,请参阅 使用 JSON.NET进行自定义序列化。

要注意的第二件事是,每个属性都用属性进行修饰,如 IsFilterable、IsSearchable、Key和 Analyzer。 这些属性直接映射到 Azure 认知搜索索引 中相应的字段属性。 FieldBuilder 类使用这些属性来构造索引的字段定义。

Hotel 类的第三个重要事项是公共属性的数据类型。 这些属性的 .NET 类型映射到索引定义中的等效字段类型。 例如,Category 字符串属性映射到 category 类型的 Edm.String 字段。 bool? 和 Edm.Boolean、 DateTimeOffset?和 Edm.DateTimeOffset 等之间存在类似的类型映射。 在 Documents.Get中, 方法记录了类型映射的特定规则。 FieldBuilder 类会为你处理此映射,但理解其原理仍然有助于在需要排查任何序列化问题时进行故障排除。

你是否注意到了 SmokingAllowed 属性?

[JsonIgnore]
public bool? SmokingAllowed => (Rooms != null) ? Array.Exists(Rooms, element => element.SmokingAllowed == true) : (bool?)null;

此属性上的JsonIgnore特性告知FieldBuilder不要将其作为字段序列化至索引。 这是创建可在应用程序中用作帮助程序的客户端计算属性的好方法。 在这种情况下,SmokingAllowed 属性将反映 Room 集合中的任何 Rooms 是否允许吸烟。 如果所有项都为 false,则表示整个酒店不允许吸烟。

某些属性(如 Address 和 Rooms)是 .NET 类的实例。 这些属性表示更复杂的数据结构,因此需要索引中具有 复杂数据类型 的字段。

Address 属性表示 Address 类中的一组多个值,定义如下:

using System;
using Microsoft.Azure.Search;
using Microsoft.Azure.Search.Models;
using Newtonsoft.Json;

namespace AzureSearch.SDKHowTo
{
    public partial class Address
    {
        [IsSearchable]
        public string StreetAddress { get; set; }

        [IsSearchable, IsFilterable, IsSortable, IsFacetable]
        public string City { get; set; }

        [IsSearchable, IsFilterable, IsSortable, IsFacetable]
        public string StateProvince { get; set; }

        [IsSearchable, IsFilterable, IsSortable, IsFacetable]
        public string PostalCode { get; set; }

        [IsSearchable, IsFilterable, IsSortable, IsFacetable]
        public string Country { get; set; }
    }
}

这个类包含用于描述美国或加拿大地址的标准值。 可以使用此类类型在索引中将逻辑字段组合在一起。

Rooms 属性表示 Room 对象的数组:

using System;
using Microsoft.Azure.Search;
using Microsoft.Azure.Search.Models;
using Newtonsoft.Json;

namespace AzureSearch.SDKHowTo
{
    public partial class Room
    {
        [IsSearchable]
        [Analyzer(AnalyzerName.AsString.EnMicrosoft)]
        public string Description { get; set; }

        [IsSearchable]
        [Analyzer(AnalyzerName.AsString.FrMicrosoft)]
        [JsonProperty("Description_fr")]
        public string DescriptionFr { get; set; }

        [IsSearchable, IsFilterable, IsFacetable]
        public string Type { get; set; }

        [IsFilterable, IsFacetable]
        public double? BaseRate { get; set; }

        [IsSearchable, IsFilterable, IsFacetable]
        public string BedOptions { get; set; }

        [IsFilterable, IsFacetable]
        public int SleepsCount { get; set; }

        [IsFilterable, IsFacetable]
        public bool? SmokingAllowed { get; set; }

        [IsSearchable, IsFilterable, IsFacetable]
        public string[] Tags { get; set; }
    }
}

.NET 中的数据模型及其相应的索引架构应设计为支持要提供给最终用户的搜索体验。 .NET 中的每个顶级对象(即索引中的文档)对应于将在用户界面中显示的搜索结果。 例如,在酒店搜索应用程序中,最终用户可能希望按酒店名称、酒店功能或特定房间的特征进行搜索。 稍后我们将介绍一些查询示例。

这一功能允许您使用自己的类与索引中的文档进行双向交互;您还可以检索搜索结果,并让 SDK 自动将其反序列化为您选择的类型,如下一部分所示。

注释

Azure 认知搜索 .NET SDK 还支持使用 Document 类动态类型化文档,该类是字段名称到字段值的键/值映射。 在设计时不知道索引架构,或者在绑定到特定模型类会不方便的情况下,这非常有用。 处理文档的 SDK 中的所有方法都具有使用 Document 类的重载,以及采用泛型类型参数的强类型重载。 本教程中的示例代码中仅使用后者。 Document 类 继承自 Dictionary<string, object>。

为何应使用可以为 null 的数据类型

在设计自己的模型类以映射到 Azure 认知搜索索引时,我们建议将值类型的属性声明为可为 null,例如,将 bool 和 int 声明为可为 null (bool? 而不是 bool)。 如果使用不可为 null 的属性,则必须 保证索引中没有文档包含相应字段的 null 值。 SDK 和 Azure 认知搜索服务都不会帮助你强制实施此服务。

这不仅仅是一个假设的问题:想象一下,向 Edm.Int32类型的现有索引添加新字段的情况。 更新索引定义后,所有文档都将具有该新字段的 null 值(因为所有类型在 Azure 认知搜索中都是可以为 null 的)。 如果随后对该字段使用具有不可为 null 的 int 属性的模型类,则在尝试检索文档时会收到如下所示的 JsonSerializationException:

Error converting value {null} to type 'System.Int32'. Path 'IntValue'.

出于此原因,我们建议在模型类中使用可为 null 的类型作为最佳做法。

使用 JSON.NET 进行自定义序列化

SDK 使用 JSON.NET 序列化和反序列化文档。 如果需要,可以自定义 JsonConverter 或 IContractResolver来实现序列化和反序列化。 有关详细信息,请参阅 JSON.NET 文档。 如果要根据应用程序调整现有模型类,以便与 Azure 认知搜索一起使用,以及其他更高级的方案,这非常有用。 例如,使用自定义序列化,可以:

  • 决定是否将模型类的某些属性作为文档字段进行存储。
  • 在代码中的属性名称与索引中的字段名称之间进行映射。
  • 创建自定义属性,这些属性可用于将属性映射到文档字段。

可以在 GitHub 上的 Azure 认知搜索 .NET SDK 单元测试中找到实现自定义序列化的示例。 一个很好的起点是 此文件夹。 它包含自定义序列化测试使用的类。

在索引中搜索文档

示例应用程序中的最后一步是搜索索引中的某些文档:

private static void RunQueries(ISearchIndexClient indexClient)
{
    SearchParameters parameters;
    DocumentSearchResult<Hotel> results;

    Console.WriteLine("Search the entire index for the term 'motel' and return only the HotelName field:\n");

    parameters =
        new SearchParameters()
        {
            Select = new[] { "HotelName" }
        };

    results = indexClient.Documents.Search<Hotel>("motel", parameters);

    WriteDocuments(results);

    Console.Write("Apply a filter to the index to find hotels with a room cheaper than $100 per night, ");
    Console.WriteLine("and return the hotelId and description:\n");

    parameters =
        new SearchParameters()
        {
            Filter = "Rooms/any(r: r/BaseRate lt 100)",
            Select = new[] { "HotelId", "Description" }
        };

    results = indexClient.Documents.Search<Hotel>("*", parameters);

    WriteDocuments(results);

    Console.Write("Search the entire index, order by a specific field (lastRenovationDate) ");
    Console.Write("in descending order, take the top two results, and show only hotelName and ");
    Console.WriteLine("lastRenovationDate:\n");

    parameters =
        new SearchParameters()
        {
            OrderBy = new[] { "LastRenovationDate desc" },
            Select = new[] { "HotelName", "LastRenovationDate" },
            Top = 2
        };

    results = indexClient.Documents.Search<Hotel>("*", parameters);

    WriteDocuments(results);

    Console.WriteLine("Search the entire index for the term 'hotel':\n");

    parameters = new SearchParameters();
    results = indexClient.Documents.Search<Hotel>("hotel", parameters);

    WriteDocuments(results);
}

每次执行查询时,此方法都会首先创建新的 SearchParameters 对象。 此对象用于为查询指定其他选项,如排序、筛选、分页和分类。 在此方法中,我们将为不同的查询设置 Filter、Select、OrderBy和 Top 属性。 此处 SearchParameters记录所有 属性。

下一步是实际执行搜索查询。 使用 Documents.Search 方法运行搜索。 对于每个查询,我们将传递要用作字符串的搜索文本(如果没有搜索文本时 "*"),以及前面创建的搜索参数。 此外,我们指定 Hotel 作为 Documents.Search 的类型参数,这指示 SDK 将搜索结果中的文档反序列化为类型为 Hotel 的对象。

最后,在每次查询之后,此方法循环访问搜索结果中的所有匹配项,将每个文档打印到控制台:

private static void WriteDocuments(DocumentSearchResult<Hotel> searchResults)
{
    foreach (SearchResult<Hotel> result in searchResults.Results)
    {
        Console.WriteLine(result.Document);
    }

    Console.WriteLine();
}

让我们依次仔细查看每个查询。 下面是执行第一个查询的代码:

parameters =
    new SearchParameters()
    {
        Select = new[] { "HotelName" }
    };

results = indexClient.Documents.Search<Hotel>("motel", parameters);

WriteDocuments(results);

在这种情况下,我们将在任何可搜索字段中搜索“motel”一词的整个索引,我们只想检索由 Select 参数指定的酒店名称。 结果如下:

Name: Secret Point Motel

Name: Twin Dome Motel

下一个查询稍微更有趣。 我们希望找到任何每晚费率小于 100 美元的客房的酒店,并仅返回酒店 ID 和说明:

parameters =
    new SearchParameters()
    {
        Filter = "Rooms/any(r: r/BaseRate lt 100)",
        Select = new[] { "HotelId", "Description" }
    };

results = indexClient.Documents.Search<Hotel>("*", parameters);

WriteDocuments(results);

此查询使用 OData $filter 表达式 Rooms/any(r: r/BaseRate lt 100) 来筛选索引中的文档。 这将使用 任意运算符 将“BaseRate lt 100”应用于房间集合中的每个项。 可以在此处 详细了解 Azure 认知搜索支持的 OData 语法。

下面是查询的结果:

HotelId: 1
Description: The hotel is ideally located on the main commercial artery of the city in the heart of New York...

HotelId: 2
Description: The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to...

接下来,我们希望找到最近装修的前两家酒店,并显示酒店名称和上次装修日期。 代码如下:

parameters =
    new SearchParameters()
    {
        OrderBy = new[] { "LastRenovationDate desc" },
        Select = new[] { "HotelName", "LastRenovationDate" },
        Top = 2
    };

results = indexClient.Documents.Search<Hotel>("*", parameters);

WriteDocuments(results);

在这种情况下,我们再次使用 OData 语法将 OrderBy 参数指定为 lastRenovationDate desc。 我们还将 Top 设置为 2,以确保仅获取前两个文档。 与之前一样,我们将 Select 设置为指定应返回哪些字段。

结果如下:

Name: Fancy Stay        Last renovated on: 6/27/2010 12:00:00 AM +00:00
Name: Roach Motel       Last renovated on: 4/28/1982 12:00:00 AM +00:00

最后,我们希望查找与“hotel”一词匹配的所有酒店名称:

parameters = new SearchParameters()
{
    SearchFields = new[] { "HotelName" }
};
results = indexClient.Documents.Search<Hotel>("hotel", parameters);

WriteDocuments(results);

下面是结果,其中包括所有字段,因为我们未指定 Select 属性:

	HotelId: 3
	Name: Triple Landscape Hotel
	...

此步骤将完成本教程,但不要在此处停止。 **后续步骤提供了其他资源,用于了解有关 Azure 认知搜索的详细信息。

后续步骤